pax_global_header00006660000000000000000000000064151624236670014526gustar00rootroot0000000000000052 comment=8995e5d11ee89f2251734e34cea711bb5a5bb911 mstpd-mstpd-8995e5d/000077500000000000000000000000001516242366700143605ustar00rootroot00000000000000mstpd-mstpd-8995e5d/.depend000066400000000000000000000020551516242366700156220ustar00rootroot00000000000000# DO NOT DELETE bridge_track.o: bridge_ctl.h ctl_functions.h mstp.h list.h netif_utils.h bridge_track.o: packet.h log.h ctl_socket_server.h driver.h libnetlink.h brmon.o: libnetlink.h bridge_ctl.h netif_utils.h epoll_loop.h ctl_main.o: ctl_socket_client.h ctl_functions.h mstp.h bridge_ctl.h list.h ctl_main.o: log.h ctl_socket_server.h ctl_socket_client.o: ctl_functions.h mstp.h bridge_ctl.h list.h log.h ctl_socket_client.o: ctl_socket_server.h ctl_socket_server.o: ctl_socket_client.h ctl_functions.h mstp.h bridge_ctl.h ctl_socket_server.o: list.h epoll_loop.h log.h ctl_socket_server.h driver_deps.o: log.h ctl_socket_server.h mstp.h bridge_ctl.h list.h epoll_loop.o: epoll_loop.h bridge_ctl.h hmac_md5.o: mstp.h bridge_ctl.h list.h libnetlink.o: libnetlink.h main.o: epoll_loop.h bridge_ctl.h netif_utils.h packet.h log.h main.o: ctl_socket_server.h mstp.h list.h mstp.o: mstp.h bridge_ctl.h list.h log.h ctl_socket_server.h netif_utils.o: log.h ctl_socket_server.h packet.o: epoll_loop.h netif_utils.h bridge_ctl.h packet.h log.h packet.o: ctl_socket_server.h mstpd-mstpd-8995e5d/.github/000077500000000000000000000000001516242366700157205ustar00rootroot00000000000000mstpd-mstpd-8995e5d/.github/workflows/000077500000000000000000000000001516242366700177555ustar00rootroot00000000000000mstpd-mstpd-8995e5d/.github/workflows/build.yml000066400000000000000000000007341516242366700216030ustar00rootroot00000000000000name: build mstpd on: [ push, pull_request ] jobs: linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: build shell: bash run: ./autogen.sh && ./configure --enable-devel && make V=s - name: shellcheck shell: bash run: shellcheck utils/ifupdown.sh.in utils/mstp_config_bridge.in utils/mstpctl-utils-functions.sh - name: distcheck shell: bash run: ./autogen.sh && ./configure && make distcheck mstpd-mstpd-8995e5d/.gitignore000066400000000000000000000014231516242366700163500ustar00rootroot00000000000000/mstpctl /mstpd *.o *.swp /bridge-stp /bridge-stp.tmp /utils/ifupdown.sh /utils/ifupdown.sh.tmp /utils/mstp_config_bridge /utils/mstp_config_bridge.tmp /utils/mstpd.service /utils/mstpd.service.tmp /utils/nm-dispatcher /utils/nm-dispatcher.tmp # autotools files /m4/lt*.m4 /m4/libtool.m4 Makefile.in aclocal.m4 ar-lib autom4te.cache compile config.guess config.h.in config.h.in~ config.sub configure depcomp install-sh ltmain.sh missing /mstpd-*.tar.gz # configure files Makefile .deps config.h config.log config.status libtool stamp-h1 # packaging files /debian/mstpd.debhelper.log /debian/debhelper-build-stamp /debian/mstpd.substvars /debian/files /debian/mstpd /debian/.debhelper /debian/autoreconf* # Generated by CI build mstpd-build-deps*.buildinfo mstpd-build-deps*.changes mstpd-mstpd-8995e5d/.travis.yml000066400000000000000000000004441516242366700164730ustar00rootroot00000000000000git: depth: 1 language: c os: linux compiler: gcc services: - docker dist: xenial addons: apt: packages: - shellcheck script: - set -e - shellcheck utils/ifupdown.sh.in utils/mstp_config_bridge.in utils/mstpctl-utils-functions.sh - ./autogen.sh && ./configure && make V=s mstpd-mstpd-8995e5d/LICENSE000066400000000000000000000431051516242366700153700ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. 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 St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. mstpd-mstpd-8995e5d/Makefile.am000066400000000000000000000133011516242366700164120ustar00rootroot00000000000000ACLOCAL_AMFLAGS = -I m4 sbin_PROGRAMS = mstpd mstpctl mstpd_SOURCES = \ main.c epoll_loop.c epoll_loop.h clock_gettime.h brmon.c \ bridge_track.c bridge_track.h driver.h bridge_ctl.h libnetlink.c \ libnetlink.h mstp.c mstp.h packet.c packet.h netif_utils.c \ netif_utils.h ctl_socket_server.c ctl_socket_server.h hmac_md5.c \ list.h log.h driver_deps.c mstpctl_SOURCES = \ ctl_main.c ctl_socket_client.c ctl_socket_client.h ctl_functions.h mstpd_CFLAGS = \ -Os -Wall -D_REENTRANT -D__LINUX__ -I. \ -D_GNU_SOURCE if ENABLE_DEVEL mstpd_CFLAGS += -g3 -O0 -Werror endif mstpctl_CFLAGS = $(mstpd_CFLAGS) EXTRA_DIST = bridge-stp.in utils/ifupdown.sh.in utils/mstp_config_bridge.in \ utils/mstpd.service.in utils/bash_completion utils/nm-dispatcher.in \ README.md README.VLANs.md mstpd.spec autogen.sh CLEANFILES = bridge-stp utils/ifupdown.sh utils/mstp_config_bridge \ utils/mstpd.service utils/nm-dispatcher dist_utilsexec_SCRIPTS = utils/ifquery dist_doc_DATA = LICENSE utilsexec_SCRIPTS = utils/ifupdown.sh utils/mstp_config_bridge dist_utilsexec_DATA = utils/mstpctl-utils-functions.sh sbin_SCRIPTS = bridge-stp dist_sysconf_DATA = bridge-stp.conf dist_man_MANS = utils/mstpctl.8 utils/mstpctl-utils-interfaces.5 AM_DISTCHECK_CONFIGURE_FLAGS = --exec_prefix=$$dc_install_base \ --libexecdir=$$dc_install_base/lib \ --with-systemdunitdir=$$dc_install_base/usr/lib/systemd/system all-local: bridge-stp utils/ifupdown.sh utils/mstp_config_bridge \ utils/mstpd.service utils/nm-dispatcher utilsexecdir=$(libexecdir)/mstpctl-utils nmlibdir=$(prefix)/lib/NetworkManager/dispatcher.d mstpdfile=$(sbindir)/mstpd mstpctlfile=$(sbindir)/mstpctl bridgestpfile=$(sbindir)/bridge-stp mstpdpidfile=$(localstatedir)/run/mstpd.pid bridgestpconffile=$(sysconfdir)/bridge-stp.conf ifupdownfile=$(utilsexecdir)/ifupdown.sh utilsfuncfile=$(utilsexecdir)/mstpctl-utils-functions.sh configbridgefile=$(utilsexecdir)/mstp_config_bridge ifqueryfile=$(utilsexecdir)/ifquery mstpd_CFLAGS += -DMSTPD_PID_FILE='"$(mstpdpidfile)"' # See https://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.69/html_node/Installation-Directory-Variables.html#index-sysconfdir-188 populate_template = sed \ -e 's|@mstpdfile[@]|$(mstpdfile)|g' \ -e 's|@bridgestpfile[@]|$(bridgestpfile)|g' \ -e 's|@mstpdpidfile[@]|$(mstpdpidfile)|g' \ -e 's|@mstpctlfile[@]|$(mstpctlfile)|g' \ -e 's|@bridgestpconffile[@]|$(bridgestpconffile)|g' \ -e 's|@ifupdownfile[@]|$(ifupdownfile)|g' \ -e 's|@utilsfuncfile[@]|$(utilsfuncfile)|g' \ -e 's|@configbridgefile[@]|$(configbridgefile)|g' \ -e 's|@ifqueryfile[@]|$(ifqueryfile)|g' bridge-stp utils/ifupdown.sh utils/mstp_config_bridge utils/mstpd.service utils/nm-dispatcher: Makefile rm -f $@ $@.tmp mkdir -p $(@D) srcdir=''; test -f ./$@.in || srcdir="$(srcdir)/"; \ $(populate_template) $${srcdir}$@.in > $@.tmp chmod 755 $@.tmp mv $@.tmp $@ bridge-stp: $(srcdir)/bridge-stp.in utils/ifupdown.sh: $(srcdir)/utils/ifupdown.sh.in utils/mstp_config_bridge: $(srcdir)/utils/mstp_config_bridge.in utils/nm-dispatcher: $(srcdir)/utils/nm-dispatcher.in sbindest=$(DESTDIR)$(sbindir) utilsdest=$(DESTDIR)$(utilsexecdir) etcdest=$(DESTDIR)$(sysconfdir) docdest=$(DESTDIR)$(docdir) bashcompdest=$(DESTDIR)$(bashcompletiondir) systemdunitdest=$(DESTDIR)$(systemdunitdir) nmlibdest=$(DESTDIR)$(nmlibdir) if ENABLE_INSTALL_IFUPDOWN_SCRIPTS IFUPDOWN_SCRIPT_INSTALL_CMDS = \ $(MKDIR_P) $(etcdest)/network/if-pre-up.d ; \ ln -sf $(utilsexecdir)/ifupdown.sh $(etcdest)/network/if-pre-up.d/mstpctl ; \ $(MKDIR_P) $(etcdest)/network/if-post-down.d ; \ ln -sf $(utilsexecdir)/ifupdown.sh $(etcdest)/network/if-post-down.d/mstpctl endif install-exec-hook: ln -sf $(sbindir)/bridge-stp $(sbindest)/mstp_restart ln -sf $(sbindir)/bridge-stp $(utilsdest)/mstpctl_restart_config $(IFUPDOWN_SCRIPT_INSTALL_CMDS) $(MKDIR_P) $(bashcompdest) $(INSTALL_DATA) $(srcdir)/utils/bash_completion $(bashcompdest)/mstpctl if [ -n "$(systemdunitdir)" ]; then \ $(MKDIR_P) $(systemdunitdest) ; \ $(INSTALL_DATA) $(top_builddir)/utils/mstpd.service $(systemdunitdest) ; \ fi if [ -d $(nmlibdir) ]; then \ $(MKDIR_P) $(nmlibdest) ; \ $(INSTALL_SCRIPT) $(top_builddir)/utils/nm-dispatcher $(nmlibdest)/45-mstpd ; \ fi $(MKDIR_P) $(docdest) $(INSTALL_DATA) $(srcdir)/README.md $(docdest)/README $(INSTALL_DATA) $(srcdir)/README.VLANs.md $(docdest)/README.VLANs uninstall-hook: -rm -f $(sbindest)/mstp_restart -rm -f $(utilsdest)/mstpctl_restart_config -rmdir $(utilsdest) -rm -f $(bashcompdest)/mstpctl -if [ -d $(etcdest)/network/if-pre-up.d ]; then \ rm -f $(etcdest)/network/if-pre-up.d/mstpctl ; \ fi -if [ -d $(etcdest)/network/if-post-down.d ]; then \ rm -f $(etcdest)/network/if-post-down.d/mstpctl ; \ fi -if [ -n "$(systemdunitdir)" ]; then \ rm -f $(systemdunitdest)/mstpd.service ; \ fi -if [ -d $(nmlibdest) ]; then \ rm -f $(nmlibdest)/45-mstpd ; \ fi -rm -f $(docdest)/README -rm -f $(docdest)/README.VLANs -rmdir $(docdest) rpm: mstpd.spec Version="$$(perl -n -e 'if(/AC_INIT[^,]+,\s+\[([^]]+)\]/) \ { $$V=$$1; $$V=~s/-/_/g; print $$V; exit; }' configure.ac)" ; \ [ -n "$$Version" ] || exit 3 ; SrcDir="$$(pwd)" ; \ mkdir -p "../rpmbuild/SOURCES" && cd "../rpmbuild/SOURCES" && \ git clone "$$SrcDir" "mstpd-$$Version" && \ rm -rf "mstpd-$$Version/.git" && \ tar -cvzf "mstpd-$$Version.tar.gz" "mstpd-$$Version" && \ rm -rf "mstpd-$$Version" && \ cd .. && mkdir -p SPECS && \ cp "$$SrcDir/mstpd.spec" SPECS/mstpd.spec && \ rpmbuild --define "_topdir $$(pwd)" -ba SPECS/mstpd.spec mstpd.spec: configure.ac Version="$$(perl -n -e 'if(/AC_INIT[^,]+,\s+\[([^]]+)\]/) \ { $$V=$$1; $$V=~s/-/_/g; print $$V; exit; }' configure.ac)" ; \ [ -n "$$Version" ] || exit 3 ; \ perl -i -p -e 's/^(\s*Version:\s+).*$$/$${1}'"$$Version/" mstpd.spec mstpd-mstpd-8995e5d/NEWS000066400000000000000000000141141516242366700150600ustar00rootroot00000000000000mstpd: Multiple Spanning Tree Protocol Daemon ============================================= Version: 0.2.0 --------------------------------------------- - README.md: update IRC channel location - Jonas Gorski - ctl_socket_server: require root for changing configuration - Jonas Gorski - mstp: validate admin port path costs - Jonas Gorski - mstp: do not allow changing Bridge Hello Time - Jonas Gorski - mstp: do not overflow calculated root path costs - Jonas Gorski - mstp: increase maxHops limit to 100 - Jonas Gorski - README.md: add packaging status - Jonas Gorski - README.md: update FID/MSTI limits - Jonas Gorski - README.md: clarify IVL/SVL and MSTI support in linux - Jonas Gorski - packet: set socket priority to TC_PRIO_CONTROL - Jonas Gorski - TCSM: fix infinite recursion for MSTI per-tree ports - Thibaut Collet Version: 0.1.1 --------------------------------------------- - add showportparams command to show only specific params of a port - Ali Forouzan - bridge-stp.in: support different versions of pidof - Alexandru Ardelean - mstpctl-utils-functions.sh: fix shellcheck warnings - Jonas Gorski - fix printing error message in bridge_bpdu_rcv() - QAQguowang <39701514+QAQguowang@users.noreply.github.com> - fix calculating costs for speeds > 65G - Jonas Gorski - ifupdown.sh.in: fix new shellcheck warnings - Jonas Gorski - libnetlink: fix socket file descriptor leak on error paths - Vincent Jardin - Fix compilation with GCC 15 / gnu23 - Jonas Gorski - Fix a stack memory information leak to client - Jonas Gorski Version: 0.1.0 --------------------------------------------- - libnetlink changes to start supporting Linux per-VLAN STP - libnetlink: Set NLA_F_NESTED in rta_nest - libnetlink: introduce rta_nest and u8, u16, u64 helpers for nesting within rtattr - Jonas Gorski - make libnetlink.h standalone - Signed-off-by: Pavel Šimerda - utils/ifquery: drop python2 support - Jan Klare - Replace brctl with ip and bridge commands - Scott Shambarger - rpm.spec: Fix mock build, requires and quoting - Scott Shambarger - build: add option to enable installation of ifupdown scripts - Alexandru Ardelean Version: 0.0.9 --------------------------------------------- - Add the "bpdu-filter-port" and "num-rx-bpdu-filtered" attributes to the JSON formatted "show" output. - Mark Gillott - Check BPDU Guard before BPDU Filter - Mark Gillott - treewide: allow room for null-char in strncpy() - Alexandru Ardelean - Handle increasing/decreasing fdelay and maxage in sync in ifcfg files. - link https://github.com/mstpd/mstpd/issues/86 - Eemil Lagerspetz - move deb packaging changes to own branch - Alexandru Ardelean - rpm.spec and ifquery: Use versioned python shebang and fix flake8 warnings - Paul Donohue - Add systemd unit file - Vincent Bernat - Fix destination copy for system unit build artifact from the build folder - Zander Erasmus - ci,github-actions: add first integration - Alexandru Ardelean - build: move -Werror flag under --enable-devel opt - Alexandru Ardelean - Don't compress man pages - David Seifert Version: 0.0.8 --------------------------------------------- - "BPDU Port Filter" option ("setportbpdufilter") - Disable STP processing on a specific port - Mark Gillott mgillott@vyatta.att-mail.com - Fix to build with musl library - fidomax adobegitler@gmail.com - Support for mstpctl batch commands - Alexandru Ardelean ardeleanalex@gmail.com Version: 0.0.7 --------------------------------------------- - JSON output format support - Anton Kikin a.kikin@tano-systems.com - More counters are now displayed - Anton Kikin a.kikin@tano-systems.com - New shellcheck warnings addressed - Paul Donohue git@PaulSD.com - Leaks fixed & some sanity stuff - Mark Gillott mgillott@vyatta.att-mail.com Version: 0.0.6 --------------------------------------------- - Tagging this version as 0.0.6, since internally versions are x.y.z, while the last 2 git tags were 0.04 & 0.05 - Fixed compilation on GCC 7 - Default protocol version is RSTP since that is what most people use. To use MSTP, users will need to use `--setforcevers mstp`. - Various fixes to `ifupdown.sh` script - Paul Donohue - Small code fixes - Lionel Flandrin Version: 0.05 --------------------------------------------- - [Internal] Integration with Travis-CI - added shellcheck validation to shell scripts - support for Debian's travis script (http://travis.debian.net/) - Build supports autotools - initial autotools by Jonas Johansson - Paul Donohue - Gerson Fernando Budke - Added support for packaging .deb & .rpm files - Paul Donohue - Luca Boccassi - Bumped default netlink socket buffers to 256k - Display the BPDU receive and transmit status info by Ian Wilson - Support to build with musl libc - Improve compatibility with generic Bourne Shell - Mantas Mikulėnas - Paul Donohue - Various improvements in mstpd's shell scripts - Paul Donohue - dvlemplgk - Stop bridges on exit to prevent loops - Felix Kaiser mstpd-mstpd-8995e5d/README.VLANs.md000066400000000000000000000036111516242366700165620ustar00rootroot00000000000000Using mstpd with Trunk Ports / VLANs ==================================== There are currently two different ways to implement spanning tree on trunk ports using Linux bridging and mstpd: 1. Single Spanning Tree (RSTP/STP) 2. Per-VLAN Spanning Tree (PVST+) Single Spanning Tree (RSTP/STP) ------------------------------- * Create a Linux bridge and attach the trunk interfaces to it. * Enable STP on the bridge. * Force mstpd to use RSTP (`mstpctl setforcevers rstp`). * Create Linux VLAN interfaces on top of the bridge interface. * If necessary, create a Linux bridge for each VLAN and attach the relevant VLAN interface to it. Do not enable STP on these VLAN bridges. This should be compatible with most switches. If a connected switch speaks MSTP, it will fall back to RSTP on our port. If a connected switch speak STP, mstpd will fall back to STP on that port. However, note that Cisco switches in 'pvst' or 'rapid-pvst' mode **will not** fall back to RSTP in this case. To use a single Spanning Tree with Cisco switches, you must put the Cisco switches in 'mst' mode (`spanning-tree mode mst`). Cisco switches in 'mst' mode will fall back to PVST+ on ports connected to other switches that speak PVST+. Per-VLAN Spanning Tree (PVST+) ------------------------------ * Create Linux VLAN interfaces on top of each trunk interface. * For each VLAN, create a Linux bridge and attach the relevant VLAN interfaces to it. * If the native VLAN is used (if the trunk may carry untagged packets), then create another Linux bridge, attach the trunk interfaces to it, and run `ebtables -t broute -A BROUTING -i -p 802_1Q -j DROP` for each trunk interface to prevent this bridge from processing tagged packets. * Enable STP on each bridge. * Force mstpd to use RSTP on each bridge (`mstpctl setforcevers rstp`). This is only compatible with other switches that speak PVST+. mstpd-mstpd-8995e5d/README.md000066400000000000000000000106351516242366700156440ustar00rootroot00000000000000mstpd: Multiple Spanning Tree Protocol Daemon ============================================= [![Build Status](https://travis-ci.org/mstpd/mstpd.svg?branch=master)](https://travis-ci.org/mstpd/mstpd) MSTPD is an open source user-space daemon licensed under GPLv2. MSTPD is reported to be compliant with IXIA ANVL RSTP test suite, with the notable exception of looped-back BPDUs (see discussion on the matter on the Implementation Features wiki page: https://github.com/mstpd/mstpd/wiki/ImplementationFeatures). **Important note!** MSTP part of the code (as opposed to STP/RSTP part) is mainly untested, so I believe it will behave unexpectedly in many situations. Don't use it in production! Official repository: https://github.com/mstpd/mstpd *IRC: Feel free to join and chat with us on #mstpd @ OFTC!* Implementation Features ----------------------- See the wiki page: https://github.com/mstpd/mstpd/wiki/ImplementationFeatures Also MSTPD includes a number of useful features which are not defined in 802.1Q-2005 standard, but are found on many commercial switches. Namely: - BPDU Guard. Added by Satish Ashok. - Bridge Assurance. Added by Satish Ashok. (WARNING: it might be dropped in the future and replaced by the AutoIsolate feature of 802.1Q-2011) - Enhanced statistics: TX/RX BPDU/TCN counters, Forwarding/Blocking transitions counters, last 2 ports which caused topology change. Added by Satish Ashok. Goals of the project -------------------- - Create reliable and well-tested MSTP code. - Get some user base - the more users of this code, the more test coverage is. - Replace rstpd. Current state ------------- The daemon depends on the bridge Linux kernel code to gather basic info about bridge, such as bridge state (up/down, STP on/off), slave interfaces for the bridge and their state (up/down). Also daemon translates CIST states to the kernel bridge slaves, so mstpd in (r)stp mode can be used as replacement for the current rstpd (and even for the in-kernel stp!). MSTP standard (802.1Q-2005) requires from the bridge to be heavily interconnected with the VLANs infrastructure, namely: - Support several (2 .. 4094) independent FIDs (Forwarding Information Databases). Each VLAN belongs to the one of FIDs, and learning of the MAC addresses is done independently in the each FID (independent learning as opposed to shared learning in the current Linux bridges); - Support several (2 .. 64) Multiple Spanning Tree Instances in addition to the CIST. Each FID belongs to the one of MSTIs or CIST; - Support per-MSTI port states (Discarding / Learning / Forwarding) so that each bridge port can have different states for different MSTIs. Linux bridging supports either IVL or SVL, but does not support dynamic allocation of VIDs to FIDs. This is controlled via the VLAN filtering attribute: - When VLAN filtering is enabled, the bridge supports 4094 VLANs with IVL, and requires configuration of all VLANs permitted; - When VLAN filtering is disabled, the bridge is not VLAN aware and only learns via the MAC address (into a separate "untagged" FID), so it does SVL. Additionally Linux bridging gained support for MSTIs in version 5.18: - VLANs configured on the bridge can be assigned to MSTIs; - Once a VLANs is assigned to a MSTI, the port states of ports that are members of that VLAN can be configured MSTPD currently does not handle this, so MSTP daemon is not as useful for the bare Linux box (except for the (R)STP case - as stated above it works with the kernel bridge well enough in this case). But there are plenty embedded cases where bridging functions are implemented by specialized hardware with support of custom drivers. For such cases MSTP daemon can be successfully utilized. The daemon code has a few hooks where driver-specific code should be inserted to control the bridge hardware. Packaging --------- MSTPD is currently packaged for the following distributions: [![Packaging status](https://repology.org/badge/vertical-allrepos/mstpd.svg)](https://repology.org/project/mstpd/versions) It is also available as a [recipe in Yocto](https://layers.openembedded.org/layerindex/recipe/340642/). ACKNOWLEDGEMENTS ---------------- Initial code was written by looking at (and shamelessly stealing some parts from): - rstpd by Srinivas Aji `` - rstplib by Alex Rozin `` and Michael Rozhavsky `` mstpd-mstpd-8995e5d/autogen.sh000077500000000000000000000000531516242366700163570ustar00rootroot00000000000000#!/bin/sh autoreconf -W portability -visf mstpd-mstpd-8995e5d/bridge-stp.conf000066400000000000000000000007751516242366700173000ustar00rootroot00000000000000# If 'y' or commented out, mstpd will be automatically started/stopped as # needed. If 'n', mstpd will not be automatically started/stopped by the # bridge-stp script. #MANAGE_MSTPD='n' # Arguments to be passed to mstpd when it is started. # -v <0-4> : Adjust log level (default is 2) #MSTPD_ARGS='-v 2' # A space-separated list of bridges for which MSTP should be used in place of # the kernel's STP implementation. If empty or commented out, MSTP will be used # for all bridges. #MSTP_BRIDGES='br0 br1' mstpd-mstpd-8995e5d/bridge-stp.in000077500000000000000000000230001516242366700167460ustar00rootroot00000000000000#!/bin/sh # # Configuration is in @bridgestpconffile@ # # `/sbin/bridge-stp ` is called by the kernel when STP is # enabled/disabled on a bridge (via `brctl stp ` or # `ip link set type bridge stp_state <0|1>`). The kernel # enables user_stp mode if that command returns 0, or enables kernel_stp mode if # that command returns any other value. # # If called with the above arguments, this script determines whether MSTP should # be used for the specified bridge (based on the "MSTP_BRIDGES" configuration # value), starts/stops mstpd if necessary, and calls # `mstpctl ` to add/remove the bridge from mstpd # (possibly in a background job after a delay; see the comments in the code # below). No further configuration is performed automatically by this script at # that time. Additional configuration is usually performed by # @ifupdownfile@ (which calls `ip link set type bridge stp_state 1` # to trigger this script to start mstpd if necessary). # # This script is not intended to be called with the above arguments directly # (not via the kernel). However, this script may be called directly as # `mstpctl_restart_config` or `/sbin/bridge-stp restart_config` to reconfigure # (using `@configbridgefile@ ` or an alternative command specified using # a "config_cmd" configuration value) all existing bridges that are using mstpd, # or called as `mstp_restart` or `/sbin/bridge-stp restart` to restart mstpd and # then reconfigure all bridges that are using it. # # To avoid kernel deadlocks, this script (and any foreground processes it runs) # must not make any changes (using brctl, ifconfig, ip, /sys/..., etc) to the # bridge or any associated kernel network interfaces in any code paths that are # used when this script is called by the kernel. # Parse arguments. CalledAs="$(basename "$0")" if [ "$CalledAs" = 'mstpctl_restart_config' ]; then action='restart_config' elif [ $# -eq 1 ] && [ "$1" = 'restart_config' ]; then action='restart_config' elif [ "$CalledAs" = 'mstp_restart' ]; then action='restart' elif [ $# -eq 1 ] && [ "$1" = 'restart' ]; then action='restart' elif [ $# -eq 2 ] && [ "$2" = 'start' ]; then action='start' bridge="$1" elif [ $# -eq 2 ] && [ "$2" = 'stop' ]; then action='stop' bridge="$1" else echo "Usage: $0 {start|stop}" >&2 echo " or: $0 {restart|restart_config}" >&2 exit 1 fi # Make sure this script is being run as root. if [ "$(id -u)" != '0' ]; then echo 'This script must be run as root' >&2 exit 1 fi # Ensure that we have a sane umask. umask 022 # Ensure that we have a sane PATH. PATH='/sbin:/usr/sbin:/bin:/usr/bin' export PATH # Define some relevant paths. mstpctl='@mstpctlfile@' mstpd='@mstpdfile@' config_cmd='@configbridgefile@' pid_file='@mstpdpidfile@' net_dir='/sys/class/net' # Set default config values. # If 'y', mstpd will be automatically started/stopped as needed. MANAGE_MSTPD='y' # Arguments to pass to mstpd when it is started. MSTPD_ARGS='' # A space-separated list of bridges for which MSTP should be used in place of # the kernel's STP implementation. If empty, MSTP will be used for all bridges. MSTP_BRIDGES='' LOGGER='logger -t bridge-stp -s' # Read the config. if [ -e '@bridgestpconffile@' ]; then . '@bridgestpconffile@' fi errmsg () { if [ -n "$LOGGER" ]; then $LOGGER "$*" || { echo >&2 "$*"; LOGGER=; } else echo >&2 "$*" fi } # Ensure that mstpctl and mstpd exist and are executable. if [ -z "$mstpctl" ] || [ ! -x "$mstpctl" ]; then errmsg "mstpctl binary does not exist or is not executable" exit 2 fi if [ "$MANAGE_MSTPD" = 'y' ]; then if [ -z "$mstpd" ] || [ ! -x "$mstpd" ]; then errmsg "mstpd binary does not exist or is not executable" exit 2 fi fi # Determine whether mstpd should manage STP for the specified bridge. # Returns 0 if mstpd should manage STP for the specified bridge, or 1 if mstpd # should not manage STP for the specified bridge. is_mstp_bridge() { if [ -z "$MSTP_BRIDGES" ]; then return 0 fi for b in $MSTP_BRIDGES; do if [ "$1" = "$b" ]; then return 0 fi done return 1 } case "$action" in start) # Make sure the specified bridge is valid. if [ ! -d "$net_dir/$bridge/bridge" ]; then errmsg "'$bridge' is not a bridge" exit 1 fi # Determine whether the specified bridge should use MSTP. if ! is_mstp_bridge "$bridge"; then echo "Ignoring bridge '$bridge' that is not listed in \$MSTP_BRIDGES" exit 10 fi # Start mstpd if necessary. if ! pidof -s mstpd >/dev/null; then if [ "$MANAGE_MSTPD" != 'y' ]; then errmsg 'mstpd is not running' exit 3 fi echo 'mstpd is not running' echo 'Starting mstpd ...' "$mstpd" $MSTPD_ARGS || exit 3 # sleep a minimal amount here so calling scripts can reach # mstpd sleep 0.2 2>/dev/null || sleep 1 # Due to kernel locks, mstpd will not respond to mstpctl until after # this script exits, so `mstpctl addbridge ` must be run as # an asynchronous background process. # On some systems (eg. OpenWrt), mstpctl will fail if it is called # too soon after mstpd is started, so the call must also be delayed. # # To avoid race conditions, any scripts that configure the bridge # immediately after calling `brctl stp on` or # `ip link set type bridge stp_state 1` should # explicitly call `mstpctl addbridge ` themselves before # configuring the bridge. (It should not hurt to call # `mstpctl addbridge ` multiple times.) # # If `mstpctl addbridge` fails, we could turn STP off and back on # again to fall back to kernel STP mode. However, that could cause # an infinite loop if mstpd is being started successfully but is # then dying before or when mstpctl connects to it. To avoid that # possibility, we instead simply turn STP off if `mstpctl addbridge` # fails. ( sleep 1 ; "$mstpctl" addbridge "$bridge" || ip link set "$bridge" type bridge stp_state 0 ) & exit 0 fi # Add bridge to mstpd. "$mstpctl" addbridge "$bridge" || exit 3 ;; stop) # Remove bridge from mstpd. "$mstpctl" delbridge "$bridge" || exit 3 # Exit if mstpd should not be stopped when it is no longer used. if [ "$MANAGE_MSTPD" != 'y' ]; then exit 0 fi # Exit if any other bridges are using mstpd. for xbridge in $(ls "$net_dir"); do # Ignore this bridge if [ "$bridge" = "$xbridge" ]; then continue fi # Ignore interfaces that are not bridges. if [ ! -e "$net_dir/$xbridge/bridge/stp_state" ]; then continue fi # Ignore bridges that should not use MSTP. if ! is_mstp_bridge "$xbridge"; then continue fi # If bridge is in user_stp mode, then it is probably using MSTP. read State < "$net_dir/$xbridge/bridge/stp_state" if [ "$State" = '2' ]; then exit 0 fi done # Kill mstpd, since no bridges are currently using it. kill $(pidof mstpd) ;; restart|restart_config) if [ "$action" = 'restart' ]; then # Kill mstpd. pids="$(pidof mstpd)" ; Err=$? if [ $Err -eq 0 ]; then echo 'Stopping mstpd ...' kill $pids fi # Start mstpd. echo 'Starting mstpd ...' "$mstpd" $MSTPD_ARGS || exit 3 fi # Reconfigure bridges. for bridge in $(ls "$net_dir"); do # Ignore interfaces that are not bridges. if [ ! -e "$net_dir/$bridge/bridge/stp_state" ]; then continue fi # Ignore bridges that should not use MSTP. if ! is_mstp_bridge "$bridge"; then continue fi # Skip bridges that have STP disabled. read State < "$net_dir/$bridge/bridge/stp_state" if [ "$State" = '0' ]; then echo echo "Skipping bridge '$bridge' that has STP disabled." echo "To configure this bridge to use MSTP, run:" echo "ip link set '$bridge' type bridge stp_state 1" continue fi # Skip bridges that are not in user_stp mode. if [ "$State" != '2' ]; then echo echo "Skipping bridge '$bridge' that is not in user_stp mode." echo "To reconfigure this bridge to use MSTP, run:" echo "ip link set '$bridge' type bridge stp_state 0 ; \\" echo " ip link set '$bridge' type bridge stp_state 1" continue fi # Add bridge to mstpd and configure. echo "Adding/configuring bridge '$bridge' ..." "$mstpctl" addbridge "$bridge" || continue if [ -x "$config_cmd" ] || type "$config_cmd" 2>/dev/null >/dev/null ; then "$config_cmd" "$bridge" fi done echo echo 'Done' ;; esac mstpd-mstpd-8995e5d/bridge_ctl.h000066400000000000000000000064631516242366700166400ustar00rootroot00000000000000/***************************************************************************** Copyright (c) 2006 EMC Corporation. Copyright (c) 2011 Factor-SPE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. Authors: Srinivas Aji Authors: Vitalii Demianets ******************************************************************************/ #ifndef BRIDGE_CTL_H #define BRIDGE_CTL_H #include #include #include typedef struct { int if_index; __u8 macaddr[ETH_ALEN]; char name[IFNAMSIZ]; bool up; } sysdep_br_data_t; typedef struct { int if_index; __u8 macaddr[ETH_ALEN]; char name[IFNAMSIZ]; bool up; int speed, duplex; } sysdep_if_data_t; #define GET_PORT_SPEED(port) ((port)->sysdeps.speed) #define GET_PORT_DUPLEX(port) ((port)->sysdeps.duplex) /* Logging macros for mstp.c - they use system dependent info */ #define ERROR_BRNAME(_br, _fmt, _args...) ERROR("%s " _fmt, \ _br->sysdeps.name, ##_args) #define INFO_BRNAME(_br, _fmt, _args...) INFO("%s " _fmt, \ _br->sysdeps.name, ##_args) #define LOG_BRNAME(_br, _fmt, _args...) LOG("%s " _fmt, \ _br->sysdeps.name, ##_args) #define ERROR_PRTNAME(_br, _prt, _fmt, _args...) ERROR("%s:%s " _fmt, \ _br->sysdeps.name, _prt->sysdeps.name, ##_args) #define INFO_PRTNAME(_br, _prt, _fmt, _args...) INFO("%s:%s " _fmt, \ _br->sysdeps.name, _prt->sysdeps.name, ##_args) #define LOG_PRTNAME(_br, _prt, _fmt, _args...) LOG("%s:%s " _fmt, \ _br->sysdeps.name, _prt->sysdeps.name, ##_args) #define ERROR_MSTINAME(_br,_prt,_ptp,_fmt,_args...) ERROR("%s:%s:%hu " _fmt, \ _br->sysdeps.name, _prt->sysdeps.name, __be16_to_cpu(ptp->MSTID), ##_args) #define INFO_MSTINAME(_br,_prt,_ptp,_fmt,_args...) INFO("%s:%s:%hu " _fmt, \ _br->sysdeps.name, _prt->sysdeps.name, __be16_to_cpu(ptp->MSTID), ##_args) #define LOG_MSTINAME(_br,_prt,_ptp,_fmt,_args...) LOG("%s:%s:%hu " _fmt, \ _br->sysdeps.name, _prt->sysdeps.name, __be16_to_cpu(ptp->MSTID), ##_args) #define SMLOG_MSTINAME(_ptp, _fmt, _args...) \ PRINT(LOG_LEVEL_STATE_MACHINE_TRANSITION, "%s: %s:%s:%hu " _fmt, \ __PRETTY_FUNCTION__, _ptp->port->bridge->sysdeps.name, \ _ptp->port->sysdeps.name, __be16_to_cpu(ptp->MSTID), ##_args) extern struct rtnl_handle rth_state; int init_bridge_ops(void); int bridge_notify(int br_index, int if_index, bool newlink, unsigned flags); void bridge_bpdu_rcv(int ifindex, const unsigned char *data, int len); void bridge_one_second(void); #endif /* BRIDGE_CTL_H */ mstpd-mstpd-8995e5d/bridge_track.c000066400000000000000000000657231516242366700171610ustar00rootroot00000000000000/***************************************************************************** Copyright (c) 2006 EMC Corporation. Copyright (c) 2011 Factor-SPE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. Authors: Srinivas Aji Authors: Vitalii Demianets ******************************************************************************/ #include #include #include #include #include #include #include #include "bridge_ctl.h" #include "ctl_functions.h" #include "netif_utils.h" #include "packet.h" #include "log.h" #include "mstp.h" #include "driver.h" #include "libnetlink.h" #ifndef SYSFS_CLASS_NET #define SYSFS_CLASS_NET "/sys/class/net" #endif static LIST_HEAD(bridges); static bridge_t * create_br(int if_index) { bridge_t *br; TST((br = calloc(1, sizeof(*br))) != NULL, NULL); /* Init system dependent info */ br->sysdeps.if_index = if_index; if (!index_to_name(if_index, br->sysdeps.name)) goto err; if (get_hwaddr(br->sysdeps.name, br->sysdeps.macaddr)) goto err; INFO("Add bridge %s", br->sysdeps.name); if(!MSTP_IN_bridge_create(br, br->sysdeps.macaddr)) goto err; list_add_tail(&br->list, &bridges); return br; err: free(br); return NULL; } static bridge_t * find_br(int if_index) { bridge_t *br; list_for_each_entry(br, &bridges, list) { if(br->sysdeps.if_index == if_index) return br; } return NULL; } static port_t * create_if(bridge_t * br, int if_index) { port_t *prt; TST((prt = calloc(1, sizeof(*prt))) != NULL, NULL); /* Init system dependent info */ prt->sysdeps.if_index = if_index; if (!index_to_port_name(if_index, prt->sysdeps.name)) goto err; if (get_hwaddr(prt->sysdeps.name, prt->sysdeps.macaddr)) goto err; int portno; if(0 > (portno = get_bridge_portno(prt->sysdeps.name))) { ERROR("Couldn't get port number for %s", prt->sysdeps.name); goto err; } if((0 == portno) || (portno > MAX_PORT_NUMBER)) { ERROR("Port number for %s is invalid (%d)", prt->sysdeps.name, portno); goto err; } INFO("Add iface %s as port#%d to bridge %s", prt->sysdeps.name, portno, br->sysdeps.name); prt->bridge = br; if(!MSTP_IN_port_create_and_add_tail(prt, portno)) goto err; return prt; err: free(prt); return NULL; } static port_t * find_if(bridge_t * br, int if_index) { port_t *prt; list_for_each_entry(prt, &br->ports, br_list) { if(prt->sysdeps.if_index == if_index) return prt; } return NULL; } static inline void delete_if(port_t *prt) { MSTP_IN_delete_port(prt); free(prt); } static inline bool delete_if_byindex(bridge_t * br, int if_index) { port_t *prt; if(!(prt = find_if(br, if_index))) return false; delete_if(prt); return true; } static bool delete_br_byindex(int if_index) { bridge_t *br; if(!(br = find_br(if_index))) return false; INFO("Delete bridge %s (%d)", br->sysdeps.name, if_index); list_del(&br->list); MSTP_IN_delete_bridge(br); free(br); return true; } void bridge_one_second(void) { bridge_t *br; list_for_each_entry(br, &bridges, list) MSTP_IN_one_second(br); } /* New MAC address is stored in addr, which also holds the old value on entry. Return true if the address changed */ static bool check_mac_address(char *name, __u8 *addr) { __u8 temp_addr[ETH_ALEN]; if(get_hwaddr(name, temp_addr)) { LOG("Error getting hw address: %s", name); /* Error. Ignore the new value */ return false; } if(memcmp(addr, temp_addr, sizeof(temp_addr)) == 0) return false; else { memcpy(addr, temp_addr, sizeof(temp_addr)); return true; } } static void set_br_up(bridge_t * br, bool up) { bool changed = false; if(up != br->sysdeps.up) { INFO("%s was %s. Set %s", br->sysdeps.name, br->sysdeps.up ? "up" : "down", up ? "up" : "down"); br->sysdeps.up = up; changed = true; } if(check_mac_address(br->sysdeps.name, br->sysdeps.macaddr)) { /* MAC address changed */ /* Notify bridge address change */ MSTP_IN_set_bridge_address(br, br->sysdeps.macaddr); } if(changed) MSTP_IN_set_bridge_enable(br, br->sysdeps.up); } static void set_if_up(port_t *prt, bool up) { INFO("Port %s : %s", prt->sysdeps.name, (up ? "up" : "down")); int speed = -1; int duplex = -1; bool changed = false; if(check_mac_address(prt->sysdeps.name, prt->sysdeps.macaddr)) { /* MAC address changed */ if(check_mac_address(prt->bridge->sysdeps.name, prt->bridge->sysdeps.macaddr)) { /* Notify bridge address change */ MSTP_IN_set_bridge_address(prt->bridge, prt->bridge->sysdeps.macaddr); } } if(!up) { /* Down */ if(prt->sysdeps.up) { prt->sysdeps.up = false; changed = true; } } else { /* Up */ int r = ethtool_get_speed_duplex(prt->sysdeps.name, &speed, &duplex); if((r < 0) || (speed < 0)) speed = 10; if((r < 0) || (duplex < 0)) duplex = 0; /* Assume half duplex */ if(speed != prt->sysdeps.speed) { prt->sysdeps.speed = speed; changed = true; } if(duplex != prt->sysdeps.duplex) { prt->sysdeps.duplex = duplex; changed = true; } if(!prt->sysdeps.up) { prt->sysdeps.up = true; changed = true; } } if(changed) MSTP_IN_set_port_enable(prt, prt->sysdeps.up, prt->sysdeps.speed, prt->sysdeps.duplex); } /* br_index == if_index means: interface is bridge master */ int bridge_notify(int br_index, int if_index, bool newlink, unsigned flags) { port_t *prt; bridge_t *br = NULL, *other_br; bool up = !!(flags & IFF_UP); bool running = up && (flags & IFF_RUNNING); LOG("br_index %d, if_index %d, newlink %d, up %d, running %d", br_index, if_index, newlink, up, running); if((br_index >= 0) && (br_index != if_index)) { if(!(br = find_br(br_index))) return -2; /* bridge not in list */ int br_flags = get_flags(br->sysdeps.name); if(br_flags >= 0) set_br_up(br, !!(br_flags & IFF_UP)); } if(br) { if(!(prt = find_if(br, if_index))) { if(!newlink) { INFO("Got DELLINK for unknown port %d on " "bridge %d", if_index, br_index); return -1; } /* Check if this interface is slave of another bridge */ list_for_each_entry(other_br, &bridges, list) { if(other_br != br) if(delete_if_byindex(other_br, if_index)) { INFO("Device %d has come to bridge %d. " "Missed notify for deletion from bridge %d", if_index, br_index, other_br->sysdeps.if_index); break; } } prt = create_if(br, if_index); } if(!prt) { ERROR("Couldn't create data for interface %d (master %d)", if_index, br_index); return -1; } if(!newlink) { delete_if(prt); return 0; } set_if_up(prt, running); /* And speed and duplex */ } else { /* Interface is not a bridge slave */ if(!newlink) { /* DELLINK not from bridge means interface unregistered. */ /* Cleanup removed bridge or removed bridge slave */ if(!delete_br_byindex(if_index)) list_for_each_entry(br, &bridges, list) { if(delete_if_byindex(br, if_index)) break; } return 0; } else { /* This may be a new link */ if(br_index == if_index) { if(!(br = find_br(br_index))) return -2; /* bridge not in list */ set_br_up(br, up); } } } return 0; } struct llc_header { __u8 dest_addr[ETH_ALEN]; __u8 src_addr[ETH_ALEN]; __be16 len8023; __u8 d_sap; __u8 s_sap; __u8 llc_ctrl; } __attribute__((packed)); /* LLC_PDU_xxx defines snitched from linux/net/llc_pdu.h */ #define LLC_PDU_LEN_U 3 /* header and 1 control byte */ #define LLC_PDU_TYPE_U 3 /* first two bits */ /* 7.12.3 of 802.1D */ #define LLC_SAP_BSPAN 0x42 static const __u8 bridge_group_address[ETH_ALEN] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 }; void bridge_bpdu_rcv(int if_index, const unsigned char *data, int len) { port_t *prt = NULL; bridge_t *br; LOG("ifindex %d, len %d", if_index, len); list_for_each_entry(br, &bridges, list) { if((prt = find_if(br, if_index))) break; } if(!prt) return; /* sanity checks */ TSTM(br == prt->bridge,, "Bridge mismatch. This bridge is '%s' but port " "'%s' belongs to bridge '%s'", br->sysdeps.name, prt->sysdeps.name, prt->bridge->sysdeps.name); TSTM(prt->sysdeps.up,, "Port '%s' should be up", prt->sysdeps.name); /* Validate Ethernet and LLC header, * maybe we can skip this check thanks to Berkeley filter in packet socket? */ struct llc_header *h; unsigned int l; TST(len > sizeof(struct llc_header),); h = (struct llc_header *)data; TST(0 == memcmp(h->dest_addr, bridge_group_address, ETH_ALEN), INFO("ifindex %d, len %d, %02hhX%02hhX%02hhX%02hhX%02hhX%02hhX", if_index, len, h->dest_addr[0], h->dest_addr[1], h->dest_addr[2], h->dest_addr[3], h->dest_addr[4], h->dest_addr[5]) ); l = __be16_to_cpu(h->len8023); TST(l <= ETH_DATA_LEN && l <= len - ETH_HLEN && l >= LLC_PDU_LEN_U, ); TST(h->d_sap == LLC_SAP_BSPAN && h->s_sap == LLC_SAP_BSPAN && (h->llc_ctrl & 0x3) == LLC_PDU_TYPE_U,); MSTP_IN_rx_bpdu(prt, /* Don't include LLC header */ (bpdu_t *)(data + sizeof(*h)), l - LLC_PDU_LEN_U); } static int br_set_state(struct rtnl_handle *rth, unsigned ifindex, __u8 state) { struct { struct nlmsghdr n; struct ifinfomsg ifi; char buf[256]; } req; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE; req.n.nlmsg_type = RTM_SETLINK; req.ifi.ifi_family = AF_BRIDGE; req.ifi.ifi_index = ifindex; addattr8(&req.n, sizeof(req.buf), IFLA_PROTINFO, state); return rtnl_talk(rth, &req.n, 0, 0, NULL, NULL, NULL); } static int br_flush_port(char *ifname) { char fname[128]; snprintf(fname, sizeof(fname), SYSFS_CLASS_NET "/%s/brport/flush", ifname); int fd = open(fname, O_WRONLY); TSTM(0 <= fd, -1, "Couldn't open flush file %s for write: %m", fname); int write_result = write(fd, "1", 1); close(fd); TST(1 == write_result, -1); return 0; } static int br_set_ageing_time(char *brname, unsigned int ageing_time) { char fname[128], str_time[32]; snprintf(fname, sizeof(fname), SYSFS_CLASS_NET "/%s/bridge/ageing_time", brname); int fd = open(fname, O_WRONLY); TSTM(0 <= fd, -1, "Couldn't open file %s for write: %m", fname); int len = sprintf(str_time, "%u", ageing_time * HZ); int write_result = write(fd, str_time, len); close(fd); TST(len == write_result, -1); return 0; } /* External actions for MSTP protocol */ void MSTP_OUT_set_state(per_tree_port_t *ptp, int new_state) { char * state_name; port_t *prt = ptp->port; bridge_t *br = prt->bridge; if(ptp->state == new_state) return; ptp->state = driver_set_new_state(ptp, new_state); switch(ptp->state) { case BR_STATE_LISTENING: state_name = "listening"; break; case BR_STATE_LEARNING: state_name = "learning"; break; case BR_STATE_FORWARDING: state_name = "forwarding"; ++(prt->num_trans_fwd); break; case BR_STATE_BLOCKING: state_name = "blocking"; ++(prt->num_trans_blk); break; default: case BR_STATE_DISABLED: state_name = "disabled"; break; } INFO_MSTINAME(br, prt, ptp, "entering %s state", state_name); /* Translate new CIST state to the kernel bridge code */ if(0 == ptp->MSTID) { /* CIST */ if(0 > br_set_state(&rth_state, prt->sysdeps.if_index, ptp->state)) ERROR_PRTNAME(br, prt, "Couldn't set kernel bridge state %s", state_name); } } /* This function initiates process of flushing * all entries for the given port in all FIDs for the * given tree. * When this process finishes, implementation should signal * this by calling MSTP_IN_all_fids_flushed(per_tree_port_t *ptp) */ void MSTP_OUT_flush_all_fids(per_tree_port_t * ptp) { port_t *prt = ptp->port; bridge_t *br = prt->bridge; /* Translate CIST flushing to the kernel bridge code */ if(0 == ptp->MSTID) { /* CIST */ if(0 > br_flush_port(prt->sysdeps.name)) ERROR_PRTNAME(br, prt, "Couldn't flush kernel bridge forwarding database"); } /* Completion signal MSTP_IN_all_fids_flushed will be called by driver */ INFO_MSTINAME(br, prt, ptp, "Flushing forwarding database"); driver_flush_all_fids(ptp); } void MSTP_OUT_set_ageing_time(port_t *prt, unsigned int ageingTime) { unsigned int actual_ageing_time; bridge_t *br = prt->bridge; actual_ageing_time = driver_set_ageing_time(prt, ageingTime); INFO_PRTNAME(br, prt, "Setting new ageing time to %u", actual_ageing_time); /* * Translate new ageing time to the kernel bridge code. * Kernel bridging code does not support per-port ageing time, * so set ageing time for the whole bridge. */ if(0 > br_set_ageing_time(br->sysdeps.name, actual_ageing_time)) ERROR_BRNAME(br, "Couldn't set new ageing time in kernel bridge"); } void MSTP_OUT_tx_bpdu(port_t *prt, bpdu_t * bpdu, int size) { char *bpdu_type, *tcflag; bridge_t *br = prt->bridge; switch(bpdu->protocolVersion) { case protoSTP: switch(bpdu->bpduType) { case bpduTypeConfig: bpdu_type = "STP-Config"; break; case bpduTypeTCN: bpdu_type = "STP-TCN"; break; default: bpdu_type = "STP-UnknownType"; } break; case protoRSTP: bpdu_type = "RST"; break; case protoMSTP: bpdu_type = "MST"; break; default: bpdu_type = "UnknownProto"; } ++(prt->num_tx_bpdu); if((protoSTP == bpdu->protocolVersion) && (bpduTypeTCN == bpdu->bpduType)) { ++(prt->num_tx_tcn); LOG_PRTNAME(br, prt, "sending %s BPDU", bpdu_type); } else { tcflag = ""; if(bpdu->flags & (1 << offsetTc)) { ++(prt->num_tx_tcn); tcflag = ", tcFlag"; } LOG_PRTNAME(br, prt, "sending %s BPDU%s", bpdu_type, tcflag); } struct llc_header h; memcpy(h.dest_addr, bridge_group_address, ETH_ALEN); memcpy(h.src_addr, prt->sysdeps.macaddr, ETH_ALEN); h.len8023 = __cpu_to_be16(size + LLC_PDU_LEN_U); h.d_sap = h.s_sap = LLC_SAP_BSPAN; h.llc_ctrl = LLC_PDU_TYPE_U; struct iovec iov[2] = { { .iov_base = &h, .iov_len = sizeof(h) }, { .iov_base = bpdu, .iov_len = size } }; packet_send(prt->sysdeps.if_index, iov, 2, sizeof(h) + size); } void MSTP_OUT_shutdown_port(port_t *prt) { if(0 > if_shutdown(prt->sysdeps.name)) ERROR_PRTNAME(prt->bridge, prt, "Couldn't shutdown port"); } /* User interface commands */ #define CTL_CHECK_BRIDGE \ bridge_t *br = find_br(br_index); \ if(NULL == br) \ { \ ERROR("Couldn't find bridge with index %d", br_index); \ return -1; \ } #define CTL_CHECK_BRIDGE_PORT \ CTL_CHECK_BRIDGE; \ port_t *prt = find_if(br, port_index); \ if(NULL == prt) \ { \ ERROR_BRNAME(br, "Couldn't find port with index %d", port_index); \ return -1; \ } #define CTL_CHECK_BRIDGE_TREE \ CTL_CHECK_BRIDGE; \ tree_t *tree; \ bool found = false; \ __be16 MSTID = __cpu_to_be16(mstid); \ list_for_each_entry(tree, &br->trees, bridge_list) \ if(tree->MSTID == MSTID) \ { \ found = true; \ break; \ } \ if(!found) \ { \ ERROR_BRNAME(br, "Couldn't find MSTI with ID %hu", mstid); \ return -1; \ } #define CTL_CHECK_BRIDGE_PERTREEPORT \ CTL_CHECK_BRIDGE_PORT; \ per_tree_port_t *ptp; \ bool found = false; \ __be16 MSTID = __cpu_to_be16(mstid); \ list_for_each_entry(ptp, &prt->trees, port_list) \ if(ptp->MSTID == MSTID) \ { \ found = true; \ break; \ } \ if(!found) \ { \ ERROR_PRTNAME(br, prt, "Couldn't find MSTI with ID %hu", mstid); \ return -1; \ } int CTL_get_cist_bridge_status(int br_index, CIST_BridgeStatus *status, char *root_port_name) { tree_t *cist; per_tree_port_t *ptp; CTL_CHECK_BRIDGE; MSTP_IN_get_cist_bridge_status(br, status); /* find root port name by root_port_id */ cist = GET_CIST_TREE(br); *root_port_name = '\0'; list_for_each_entry(ptp, &cist->ports, tree_list) if(ptp->portId == status->root_port_id) { strncpy(root_port_name, ptp->port->sysdeps.name, IFNAMSIZ); break; } return 0; } int CTL_get_msti_bridge_status(int br_index, __u16 mstid, MSTI_BridgeStatus *status, char *root_port_name) { per_tree_port_t *ptp; CTL_CHECK_BRIDGE_TREE; MSTP_IN_get_msti_bridge_status(tree, status); /* find root port name by root_port_id */ *root_port_name = '\0'; list_for_each_entry(ptp, &tree->ports, tree_list) if(ptp->portId == status->root_port_id) { strncpy(root_port_name, ptp->port->sysdeps.name, IFNAMSIZ); break; } return 0; } int CTL_set_cist_bridge_config(int br_index, CIST_BridgeConfig *cfg) { CTL_CHECK_BRIDGE; return MSTP_IN_set_cist_bridge_config(br, cfg); } int CTL_set_msti_bridge_config(int br_index, __u16 mstid, __u8 bridge_priority) { CTL_CHECK_BRIDGE_TREE; return MSTP_IN_set_msti_bridge_config(tree, bridge_priority); } int CTL_get_cist_port_status(int br_index, int port_index, CIST_PortStatus *status) { CTL_CHECK_BRIDGE_PORT; MSTP_IN_get_cist_port_status(prt, status); return 0; } int CTL_get_msti_port_status(int br_index, int port_index, __u16 mstid, MSTI_PortStatus *status) { CTL_CHECK_BRIDGE_PERTREEPORT; MSTP_IN_get_msti_port_status(ptp, status); return 0; } int CTL_set_cist_port_config(int br_index, int port_index, CIST_PortConfig *cfg) { CTL_CHECK_BRIDGE_PORT; return MSTP_IN_set_cist_port_config(prt, cfg); } int CTL_set_msti_port_config(int br_index, int port_index, __u16 mstid, MSTI_PortConfig *cfg) { CTL_CHECK_BRIDGE_PERTREEPORT; return MSTP_IN_set_msti_port_config(ptp, cfg); } int CTL_port_mcheck(int br_index, int port_index) { CTL_CHECK_BRIDGE_PORT; return MSTP_IN_port_mcheck(prt); } int CTL_set_debug_level(int level) { INFO("level %d", level); log_level = level; return 0; } int CTL_get_mstilist(int br_index, int *num_mstis, __u16 *mstids) { CTL_CHECK_BRIDGE; return MSTP_IN_get_mstilist(br, num_mstis, mstids) ? 0 : -1; } int CTL_create_msti(int br_index, __u16 mstid) { CTL_CHECK_BRIDGE; if((!driver_create_msti(br, mstid)) || (!MSTP_IN_create_msti(br, mstid))) return -1; return 0; } int CTL_delete_msti(int br_index, __u16 mstid) { CTL_CHECK_BRIDGE; if((!driver_delete_msti(br, mstid)) || (!MSTP_IN_delete_msti(br, mstid))) return -1; return 0; } int CTL_get_mstconfid(int br_index, mst_configuration_identifier_t *cfg) { CTL_CHECK_BRIDGE; *cfg = br->MstConfigId; return 0; } int CTL_set_mstconfid(int br_index, __u16 revision, __u8 *name) { CTL_CHECK_BRIDGE; MSTP_IN_set_mst_config_id(br, revision, name); return 0; } int CTL_get_vids2fids(int br_index, __u16 *vids2fids) { CTL_CHECK_BRIDGE; memcpy(vids2fids, br->vid2fid, sizeof(br->vid2fid)); return 0; } int CTL_get_fids2mstids(int br_index, __u16 *fids2mstids) { CTL_CHECK_BRIDGE; int i; for(i = 0; i < COUNT_OF(br->fid2mstid); ++i) fids2mstids[i] = __be16_to_cpu(br->fid2mstid[i]); return 0; } int CTL_set_vid2fid(int br_index, __u16 vid, __u16 fid) { CTL_CHECK_BRIDGE; return MSTP_IN_set_vid2fid(br, vid, fid) ? 0 : -1; } int CTL_set_fid2mstid(int br_index, __u16 fid, __u16 mstid) { CTL_CHECK_BRIDGE; return MSTP_IN_set_fid2mstid(br, fid, mstid) ? 0 : -1; } int CTL_set_vids2fids(int br_index, __u16 *vids2fids) { CTL_CHECK_BRIDGE; return MSTP_IN_set_all_vids2fids(br, vids2fids) ? 0 : -1; } int CTL_set_fids2mstids(int br_index, __u16 *fids2mstids) { CTL_CHECK_BRIDGE; return MSTP_IN_set_all_fids2mstids(br, fids2mstids) ? 0 : -1; } int CTL_add_bridges(int *br_array, int* *ifaces_lists) { int i, j, ifcount, brcount = br_array[0]; bridge_t *br, *other_br; port_t *prt, *nxt; int br_flags, if_flags; int *if_array; bool found; for(i = 1; i <= brcount; ++i) { if(NULL == (br = find_br(br_array[i]))) { if(NULL == (br = create_br(br_array[i]))) { ERROR("Couldn't create data for bridge interface %d", br_array[i]); return -1; } if(0 <= (br_flags = get_flags(br->sysdeps.name))) set_br_up(br, !!(br_flags & IFF_UP)); } if_array = ifaces_lists[i - 1]; ifcount = if_array[0]; /* delete all interfaces which are not in list */ list_for_each_entry_safe(prt, nxt, &br->ports, br_list) { found = false; for(j = 1; j <= ifcount; ++j) { if(prt->sysdeps.if_index == if_array[j]) { found = true; break; } } if(!found) delete_if(prt); } /* add all new interfaces from the list */ for(j = 1; j <= ifcount; ++j) { if(NULL != find_if(br, if_array[j])) continue; /* Check if this interface is slave of another bridge */ list_for_each_entry(other_br, &bridges, list) { if(other_br != br) if(delete_if_byindex(other_br, if_array[j])) { INFO("Device %d has come to bridge %s. " "Missed notify for deletion from bridge %s", if_array[j], br->sysdeps.name, other_br->sysdeps.name); break; } } if(NULL == (prt = create_if(br, if_array[j]))) { INFO("Couldn't create data for interface %d (master %s)", if_array[j], br->sysdeps.name); continue; } if(0 <= (if_flags = get_flags(prt->sysdeps.name))) set_if_up(prt, (IFF_UP | IFF_RUNNING) == (if_flags & (IFF_UP | IFF_RUNNING)) ); } } return 0; } int CTL_del_bridges(int *br_array) { int i, brcount = br_array[0]; for(i = 1; i <= brcount; ++i) delete_br_byindex(br_array[i]); return 0; } int bridge_track_fini(void) { INFO("Stopping all bridges"); bridge_t *br; list_for_each_entry(br, &bridges, list) { set_br_up(br, false); } return 0; } mstpd-mstpd-8995e5d/bridge_track.h000066400000000000000000000020431516242366700171500ustar00rootroot00000000000000/***************************************************************************** This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. ******************************************************************************/ #ifndef MSTPD_BRIDGE_TRACK_H #define MSTPD_BRIDGE_TRACK_H int bridge_track_fini(void); #endif mstpd-mstpd-8995e5d/brmon.c000066400000000000000000000120601516242366700156400ustar00rootroot00000000000000/* * brmon.c RTnetlink listener. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Authors: Stephen Hemminger * Modified by Srinivas Aji * for use in RSTP daemon. - 2006-09-01 * Modified by Vitalii Demianets * for use in MSTP daemon. - 2011-07-18 */ #include #include #include #include #include #include "log.h" #include "libnetlink.h" #include "bridge_ctl.h" #include "netif_utils.h" #include "epoll_loop.h" /* RFC 2863 operational status */ enum { IF_OPER_UNKNOWN, IF_OPER_NOTPRESENT, IF_OPER_DOWN, IF_OPER_LOWERLAYERDOWN, IF_OPER_TESTING, IF_OPER_DORMANT, IF_OPER_UP, }; /* link modes */ enum { IF_LINK_MODE_DEFAULT, IF_LINK_MODE_DORMANT, /* limit upward transition to dormant */ }; static const char *port_states[] = { [BR_STATE_DISABLED] = "disabled", [BR_STATE_LISTENING] = "listening", [BR_STATE_LEARNING] = "learning", [BR_STATE_FORWARDING] = "forwarding", [BR_STATE_BLOCKING] = "blocking", }; static struct rtnl_handle rth; static struct epoll_event_handler br_handler; struct rtnl_handle rth_state; static int dump_msg(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) { struct ifinfomsg *ifi = NLMSG_DATA(n); struct rtattr * tb[IFLA_MAX + 1]; int len = n->nlmsg_len; char b1[IFNAMSIZ]; int af_family; bool newlink; int br_index; if(n->nlmsg_type == NLMSG_DONE) return 0; len -= NLMSG_LENGTH(sizeof(*ifi)); if(len < 0) { return -1; } af_family = ifi->ifi_family; if(af_family != AF_BRIDGE && af_family != AF_UNSPEC) return 0; if(n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK) return 0; parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len); /* Check if we got this from bonding */ if(tb[IFLA_MASTER] && af_family != AF_BRIDGE) return 0; if(tb[IFLA_IFNAME] == NULL) { ERROR("BUG: nil ifname\n"); return -1; } if(n->nlmsg_type == RTM_DELLINK) LOG("Deleted "); LOG("%d: %s ", ifi->ifi_index, (char*)RTA_DATA(tb[IFLA_IFNAME])); if(tb[IFLA_OPERSTATE]) { __u8 state = *(__u8*)RTA_DATA(tb[IFLA_OPERSTATE]); switch (state) { case IF_OPER_UNKNOWN: LOG("Unknown "); break; case IF_OPER_NOTPRESENT: LOG("Not Present "); break; case IF_OPER_DOWN: LOG("Down "); break; case IF_OPER_LOWERLAYERDOWN: LOG("Lowerlayerdown "); break; case IF_OPER_TESTING: LOG("Testing "); break; case IF_OPER_DORMANT: LOG("Dormant "); break; case IF_OPER_UP: LOG("Up "); break; default: LOG("State(%d) ", state); } } if(tb[IFLA_MTU]) LOG("mtu %u ", *(int*)RTA_DATA(tb[IFLA_MTU])); if(tb[IFLA_MASTER]) { LOG("master %s ", if_indextoname(*(int*)RTA_DATA(tb[IFLA_MASTER]), b1)); } if(tb[IFLA_PROTINFO]) { uint8_t state = *(uint8_t *)RTA_DATA(tb[IFLA_PROTINFO]); if(state <= BR_STATE_BLOCKING) LOG("state %s", port_states[state]); else LOG("state (%d)", state); } newlink = (n->nlmsg_type == RTM_NEWLINK); if(tb[IFLA_MASTER]) br_index = *(int*)RTA_DATA(tb[IFLA_MASTER]); else if(is_bridge((char*)RTA_DATA(tb[IFLA_IFNAME]))) br_index = ifi->ifi_index; else br_index = -1; bridge_notify(br_index, ifi->ifi_index, newlink, ifi->ifi_flags); return 0; } static inline void br_ev_handler(uint32_t events, struct epoll_event_handler *h) { if(rtnl_listen(&rth, dump_msg, stdout) < 0) { ERROR("Error on bridge monitoring socket\n"); } } int init_bridge_ops(void) { if(rtnl_open(&rth, RTMGRP_LINK) < 0) { ERROR("Couldn't open rtnl socket for monitoring\n"); return -1; } if(rtnl_open(&rth_state, 0) < 0) { ERROR("Couldn't open rtnl socket for setting state\n"); return -1; } if(rtnl_wilddump_request(&rth, PF_BRIDGE, RTM_GETLINK) < 0) { ERROR("Cannot send dump request: %m\n"); return -1; } if(rtnl_dump_filter(&rth, dump_msg, stdout, NULL, NULL) < 0) { ERROR("Dump terminated\n"); return -1; } if(fcntl(rth.fd, F_SETFL, O_NONBLOCK) < 0) { ERROR("Error setting O_NONBLOCK: %m\n"); return -1; } br_handler.fd = rth.fd; br_handler.arg = NULL; br_handler.handler = br_ev_handler; if(add_epoll(&br_handler) < 0) return -1; return 0; } mstpd-mstpd-8995e5d/broadcom_xstrata/000077500000000000000000000000001516242366700177145ustar00rootroot00000000000000mstpd-mstpd-8995e5d/broadcom_xstrata/README000066400000000000000000000034571516242366700206050ustar00rootroot00000000000000Synopsis: This directory contains example implementation of driver_deps.c for the Broadcom Xstrata as well as some configuration tools. Author: Vladimir Cotfas Description: Broadcom on Xstrata has a Control Plane MIPS CPU that can configure/control the switch. They have a few kernel modules for passing commands to the hardware and a "knet" multiplex pseudo-Ethernet module. The bulk of their logic is in a user-mode application named "bcm.user". There is a CLI for it called "bcm" but it's rather stupid and needs to run on a pty. I wrote bcmexp.c as a means of feeding CLI commands to bcm/bcm.user. Typically one sets up one pseudo-Ethernet interface per physical port via knet. The file knet.soc shows our arrangement for the two upstream ports (known to Linux as GbE1 and GbE1 and to BCM as ge3 and ge4 respectively). This .soc file is injected into BCM at startup via some Broadcom scripts. The thing to notice is the priority and the "destid" which is the numeric ID of the interface as used by knet. This will be used later. We also have "eth0" which is a sink for both gig ports. As gathered from the soc file no packets actually reach GbE1/GbE2 from the switch fabric in normal operation. Then stp.soc comes into play: it configures the switch fabric to except STP BPDUs to the control plane as "fp" rule "31". Then comes two lines of knet configuration that direct the excepted packets into GbE1 and GbE2 based on ingress ports. Note the destid. This soc file is loaded into BCM when mstpd starts in the startup script stp.sh. Both gig ports are switched to Blocking state. The startup script puts both gig ports into a bridge and goes through the motions of starting mstpd. The last bit released as per GPL is driver_deps.c which sends commands to BCM to switch port states.mstpd-mstpd-8995e5d/broadcom_xstrata/bcmexp.c000066400000000000000000000202311516242366700213340ustar00rootroot00000000000000/* * Based on Bill Evans's getmale.c * * Copyright (C) 2012 by Vladimir Cotfas * * Released under GPLv2 */ #include #include #include #include #include #include #include #include #include #include #include #include #define MPROMPT "BCM.0> " int pty_phyle; char recent_input[8]; struct termios tp1; struct termios tp2; int at_main_prompt(void); void log_pty_data(int the_character); int send_command(char *the_string, int add_cr, int expect_prompt); int send_constant(char *the_string); int wait_for_main_prompt(int bell); int wait_for_one_character(void); int wait_for_password_prompt(int bell); int wait_for_string(char *the_string, int bell); void wait_for_zero_status(int bell); int at_main_prompt(void) { // printf("[%s]\b", recent_input); fflush(stdout); return strstr(recent_input, MPROMPT) != NULL; } /* at_main_prompt() */ char outbuf[81920] = {0}; void p_printable(const char* buf, const int len) { if(buf == NULL || len < 1) return; int i; for(i = 0; i < len; i++) { if(buf[i] == '\n' || isprint(buf[i])) { strncat(outbuf, (buf+i), 1); if(buf[i] == '\n') { do { if(!strncmp(outbuf, "[H[J", 4)) continue; if(!strncmp(outbuf, MPROMPT, strlen(MPROMPT))) continue; write(STDOUT_FILENO, outbuf, strlen(outbuf)); } while(0); outbuf[0] = '\0'; } } } } void log_pty_data(int the_character) { int jndex; char the_buffer[4]; the_buffer[0] = the_character; p_printable(the_buffer, 1); //write(STDOUT_FILENO, the_buffer, 1); for (jndex = 0; jndex < sizeof(recent_input) - 1; jndex++) { recent_input[jndex] = recent_input[jndex + 1]; } recent_input[jndex] = the_buffer[0]; } /* log_pty_data() */ int send_command(char *the_string, int add_cr, int expect_prompt) { int function_result; function_result = send_constant(the_string); if (function_result < 1) { return function_result; } if (add_cr) { function_result = send_constant("\r"); if (function_result < 1) { return function_result; } } if (expect_prompt) { function_result = wait_for_one_character(); if (function_result < 1) { return function_result; } function_result = wait_for_main_prompt(expect_prompt - 1); if (function_result < 1) { return function_result; } } return function_result; } /* send_command() */ int send_constant(char *the_string) { int input_index; int max_fd; int select_result; char the_buffer[4]; ssize_t read_result; struct timeval timeout; fd_set read_fds; fd_set write_fds; input_index = 0; for (;;) { FD_ZERO(&read_fds); if (0) { FD_SET(pty_phyle, &read_fds); } FD_ZERO(&write_fds); FD_SET(pty_phyle, &write_fds); max_fd = pty_phyle; timeout.tv_sec = 1; timeout.tv_usec = 0; select_result = select(max_fd + 1, &read_fds, &write_fds, NULL, &timeout); if (select_result < 1) { if (0) { the_buffer[0] = '.'; //write(STDOUT_FILENO, the_buffer, 1); p_printable(the_buffer, 1); } continue; /* <--------- */ } if (FD_ISSET(pty_phyle, &read_fds)) { read_result = read(pty_phyle, the_buffer, 1); if (read_result < 0) { return -1; /* <--------- */ } if (read_result == 0) { fprintf(stderr, "read(pty) returned 0\n"); return 0; } log_pty_data(the_buffer[0]); } else if (FD_ISSET(pty_phyle, &write_fds)) { if (the_string[input_index] == 0) { return 1; /* <--------- */ } the_buffer[0] = the_string[input_index++]; write(pty_phyle, the_buffer, 1); } } } /* send_constant() */ int wait_for_main_prompt(int bell) { int max_fd; int select_result; char the_buffer[4]; ssize_t read_result; struct timeval timeout; fd_set read_fds; for (;;) { FD_ZERO(&read_fds); FD_SET(pty_phyle, &read_fds); max_fd = pty_phyle; timeout.tv_sec = 1; timeout.tv_usec = 0; select_result = select(max_fd + 1, &read_fds, NULL, NULL, &timeout); if (select_result < 1) { if (0) { the_buffer[0] = '.'; //write(STDOUT_FILENO, the_buffer, 1); p_printable(the_buffer, 1); } continue; /* <--------- */ } if (FD_ISSET(pty_phyle, &read_fds)) { read_result = read(pty_phyle, the_buffer, 1); if (read_result < 0) { return -1; /* <--------- */ } if (read_result == 0) { fprintf(stderr, "read(pty) returned 0\n"); return 0; } log_pty_data(the_buffer[0]); if (at_main_prompt()) { if (bell) { the_buffer[0] = 7; //write(STDOUT_FILENO, the_buffer, 1); p_printable(the_buffer, 1); } return 1; } } } } /* wait_for_main_prompt() */ int wait_for_one_character(void) { int max_fd; int select_result; char the_buffer[4]; ssize_t read_result; struct timeval timeout; fd_set read_fds; for (;;) { FD_ZERO(&read_fds); FD_SET(pty_phyle, &read_fds); max_fd = pty_phyle; timeout.tv_sec = 1; timeout.tv_usec = 0; select_result = select(max_fd + 1, &read_fds, NULL, NULL, &timeout); if (select_result < 1) { if (0) { the_buffer[0] = '.'; //write(STDOUT_FILENO, the_buffer, 1); p_printable(the_buffer, 1); } continue; /* <--------- */ } if (FD_ISSET(pty_phyle, &read_fds)) { read_result = read(pty_phyle, the_buffer, 1); if (read_result < 0) { return -1; /* <--------- */ } if (read_result == 0) { fprintf(stderr, "read(pty) returned 0\n"); return 0; } log_pty_data(the_buffer[0]); return 1; } } } /* wait_for_one_character() */ int wait_for_string(char *the_string, int bell) { int found_it; int jndex; int kndex; int max_fd; int select_result; char the_buffer[4]; ssize_t read_result; struct timeval timeout; fd_set read_fds; for (;;) { FD_ZERO(&read_fds); FD_SET(pty_phyle, &read_fds); max_fd = pty_phyle; timeout.tv_sec = 1; timeout.tv_usec = 0; select_result = select(max_fd + 1, &read_fds, NULL, NULL, &timeout); if (select_result < 1) { if (0) { the_buffer[0] = '.'; //write(STDOUT_FILENO, the_buffer, 1); p_printable(the_buffer, 1); } continue; /* <--------- */ } if (FD_ISSET(pty_phyle, &read_fds)) { read_result = read(pty_phyle, the_buffer, 1); if (read_result < 0) { return -1; /* <--------- */ } if (read_result == 0) { fprintf(stderr, "read(pty) returned 0\n"); return 0; } log_pty_data(the_buffer[0]); found_it = 1; jndex = sizeof(recent_input) - 1; kndex = strlen(the_string) - 1; for (;;) { if ((jndex < 0) || (kndex < 0)) { break; /* <--------- */ } if (recent_input[jndex] != the_string[kndex]) { found_it = 0; break; /* <--------- */ } jndex--; kndex--; } if (found_it) { if (bell) { the_buffer[0] = 7; //write(STDOUT_FILENO, the_buffer, 1); p_printable(the_buffer, 1); } return 1; } } } } /* wait_for_string() */ /*--------------------------------------------------------------------------*/ int main(int argc, char* argv[]) { int i; pid_t the_child; struct winsize window_size; if(argc < 2) return 0; char cmd[256] = {0}; for(i = 1; i < argc; i++) { if(i != 1) strncat(cmd, " ", 255); strncat(cmd, argv[i], 255); } strncat(cmd, "\n", 255); recent_input[sizeof(recent_input) - 1] = 0; ioctl(STDIN_FILENO, TIOCGWINSZ, &window_size); the_child = forkpty(&pty_phyle, NULL, NULL, &window_size); if (the_child == 0) { execl("/bin/bcm", "bcm", NULL); exit(0); } tcgetattr(0, &tp1); tp2 = tp1; tp2.c_iflag &= ~ICRNL; tp2.c_lflag &= ~ICANON; tp2.c_lflag &= ~ECHO; tp2.c_cc[VMIN] = 1; tp2.c_cc[VTIME] = 0; tp2.c_cc[VINTR] = 0xFF; tp2.c_cc[VSUSP] = 0xFF; tp2.c_cc[VQUIT] = 0xFF; if (tcsetattr(0, TCSANOW, &tp2) != 0) { fprintf(stderr, "setting attributes failed\n"); } //sleep(1); wait_for_main_prompt(0); send_constant(cmd); wait_for_string(MPROMPT,0); wait_for_string(MPROMPT,0); tcsetattr(0, TCSANOW, &tp1); return 0; } mstpd-mstpd-8995e5d/broadcom_xstrata/driver_deps.c000066400000000000000000000054411516242366700223720ustar00rootroot00000000000000/* * driver_deps.c Driver-specific code. * * This program is free software; you can redistribute it and/or * modify it under the terms of the 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: Vitalii Demianets * Authors: Vladimir Cotfas -- Broadcom Xstrata support */ #include #include #include #include #include #include "log.h" #include "mstp.h" static const char* port_states_bcm[] = { [BR_STATE_DISABLED] = "Disable", [BR_STATE_LISTENING] = "LIsten", [BR_STATE_LEARNING] = "LEarn", [BR_STATE_FORWARDING] = "Forward", [BR_STATE_BLOCKING] = "Disable", }; static char* if_linux2bcm(const char* name) { if((NULL == name) || ('\0' == name[0])) return "???"; if(!strcmp(name, "GbE1")) return "ge3"; if(!strcmp(name, "GbE2")) return "ge4"; return "???"; } /* * Set new state (BR_STATE_xxx) for the given port and MSTI. * Return new actual state (BR_STATE_xxx) from driver. */ int driver_set_new_state(per_tree_port_t *ptp, int new_state) { unsigned int port; port_t *ifc = ptp->port; char cmd[257] = {0}; snprintf(cmd, 256, "bcmexp port %s stp=%s &>/dev/null", if_linux2bcm(ifc->sysdeps.name), port_states_bcm[new_state]); Dprintf(2, "CMD: %s\n", cmd); system(cmd); return new_state; } bool driver_create_msti(bridge_t *br, __u16 mstid) { /* TODO: send "create msti" command to driver */ return true; } bool driver_delete_msti(bridge_t *br, __u16 mstid) { /* TODO: send "delete msti" command to driver */ return true; } /* * Flush L2 forwarding table for a given port */ void driver_flush_all_fids(per_tree_port_t *ptp) { unsigned int port; port_t *ifc = ptp->port; char cmd[257] = {0}; snprintf(cmd, 256, "bcmexp l2 clear port=%s &>/dev/null", if_linux2bcm(ifc->sysdeps.name)); Dprintf(2, "CMD: %s\n", cmd); system(cmd); /* * TODO: Confirm that L2 forwarding table is _really_ flushed * when bcmexp returns. If that is not true and after return from * bcmexp the flushing is still in process, then we should wait for the * end of that process (preferably in asynchronous manner) and only than * call MSTP_IN_all_fids_flushed(). */ MSTP_IN_all_fids_flushed(ptp); } /* * Set new ageing time (in seconds) for the bridge. * Return new actual ageing time from driver (the ageing timer granularity * in the hardware can be more than 1 sec) */ unsigned int driver_set_ageing_time(bridge_t *br, unsigned int ageingTime) { /* TODO: do set new ageing time */ return ageingTime; } mstpd-mstpd-8995e5d/broadcom_xstrata/etc-init.d-stp.sh000066400000000000000000000032321516242366700230120ustar00rootroot00000000000000#!/bin/sh # # Copyright (C) 2012 by Phybridge Inc # PATH=/sbin:/usr/sbin:/bin:/usr/bin # source our service control routines [ -r /etc/polre/svc-ctl ] && . /etc/polre/svc-ctl # spanning tree service SVC=stp MODPATH=/sbin DAEMON=mstpd DAEMONARGS= # bridge ID BRID=br0 # lowest BRPRIO=0 GBPORTS="GbE1 GbE2" GBCOST=4 start() { [ "$VERBOSE" != no ] || echo -n "Starting Spanning Tree: " bcmexp rcload /root/stp.soc # launch userland mstpd daemon [ -f $MODPATH/$DAEMON ] || exit 1 start-stop-daemon -S -q --exec $MODPATH/$DAEMON -- $DAEMONARGS # create the bridge, add the ports, adjust the costs brctl addbr $BRID || (echo "nope, already running"; exit 0) brctl setbridgeprio $BRID $BRPRIO brctl addif $BRID $GBPORTS for port in $GBPORTS; do brctl setpathcost $BRID $port $GBCOST; done # turn it on & bring interface up brctl stp $BRID on ifconfig $BRID up /sbin/mstpctl setforcevers br0 stp [ "$VERBOSE" != no ] && echo "OK" } stop() { # undo what start did mstpctl delbridge br0 ifconfig $BRID down brctl stp $BRID off brctl delif $BRID $STKPORTS $GBPORTS brctl delbr $BRID start-stop-daemon -K -q -p /var/run/$DAEMON.pid } restart() { stop sleep 1 start } status() { brctl show brctl showstp $BRID brctl showmacs $BRID } case "$1" in start) if enabled then start else [ "$VERBOSE" != no ] && echo "$SVC disabled" && exit 0 fi ;; stop) stop ;; restart|reload) restart ;; enable) enable ;; status) status ;; disable) disable stop ;; *) echo $"Usage: $0 {start|stop|restart|enable|disable|status}" exit 1 esac exit $? mstpd-mstpd-8995e5d/broadcom_xstrata/knet.soc000066400000000000000000000012411516242366700213610ustar00rootroot00000000000000# Copyright (C) 2012 by Phybridge Inc # Released under GPLv2 # ... knet netif create RCPU=no ifname=GbE1 p=4 knet filter create desttype=netif destid=4 striptag=1 ingport=4 description=GbE1 priority=10 knet netif create RCPU=no ifname=GbE2 p=5 knet filter create desttype=netif destid=5 striptag=1 ingport=5 description=GbE2 priority=10 # ... knet filter create desttype=netif destid=54 striptag=1 ingport=4 description=ge4e0 priority=5 knet filter create desttype=netif destid=54 striptag=1 ingport=5 description=ge5e0 priority=5 knet filter create desttype=netif destid=54 striptag=1 reason=protocol description=protocola priority=5 # ... pw report -raw pw start mstpd-mstpd-8995e5d/broadcom_xstrata/stp.soc000066400000000000000000000012151516242366700212270ustar00rootroot00000000000000# Copyright (C) 2012 by Phybridge Inc # Released under GPLv2 port ge3 stp=block port ge4 stp=block fp qset clear fp qset add dstmac fp qset add ethertype fp qset add inports #fp qset add outports fp qset add drop fp group create 4 777 1 # This is STP and LLDP as LLDP uses the same mcast MAC as STP fp entry create 777 512 fp qual 512 dstmac data=01:80:c2:00:00:00 mask=ff:ff:ff:00:00:00 fp action add 512 copytocpu 1 31 fp entry install 512 knetctrl filter create DestType=NetIF DestID=4 StripTag=yes IngPort=ge3 FPRule=31 DESCription="ge3-fp" knetctrl filter create DestType=NetIF DestID=5 StripTag=yes IngPort=ge4 FPRule=31 DESCription="ge4-fp" mstpd-mstpd-8995e5d/clock_gettime.h000066400000000000000000000021241516242366700173410ustar00rootroot00000000000000/* * clock_gettime.h - clock_gettime() support with fallback to gettimeofday() * * This program is free software; you can redistribute it and/or * modify it under the terms of the 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: Alexandru Ardelean */ #ifndef __CLOCK_GETTIME_H__ #define __CLOCK_GETTIME_H__ #include #include #include #if HAVE_STRUCT_TIMESPEC // Nothing #else struct timespec { long tv_sec; /* Seconds. */ long tv_nsec; /* Nanoseconds. */ } #endif /* HAVE_STRUCT_TIMESPEC */ #if HAVE_CLOCK_GETTIME // Nothing #else #ifndef CLOCK_MONOTONIC #define CLOCK_MONOTONIC 1 #endif static inline int clock_gettime (clockid_t clock, struct timespec *ts) { struct timeval tv; if (gettimeofday (&tv, NULL)) return -1; ts->tv_sec = tv.tv_sec; ts->tv_nsec = tv.tv_usec * 1000; return 0; } #endif /* HAVE_CLOCK_GETTIME */ #endif /* __CLOCK_GETTIME_H__ */ mstpd-mstpd-8995e5d/configure.ac000066400000000000000000000075641516242366700166620ustar00rootroot00000000000000AC_PREREQ([2.61]) AC_INIT([mstpd], [0.2.0], [https://github.com/mstpd/mstpd]) # bridge-stp must be installed in /sbin/, and the associated scripts and # executables should be installed outside of /usr/ by default. If `./configure` # was run without specifying `--exec-prefix=...`, then set the default # exec_prefix so these files will be installed in the correct paths by default. # AC_PREFIX_DEFAULT(...) can be used to override the default prefix, but there # doesn't seem to be any standard macro to override the default exec_prefix, so # just do it manually. test "x$exec_prefix" = xNONE && exec_prefix='' # bridge-stp.conf should be installed in /etc/ rather than /usr/local/etc/ by # default. If `./configure` was run without specifying `--prefix=...` or # `--sysconfdir=...`, then set the default sysconfdir appropriately. # There is no standard macro to override the default sysconfdir, so just do it # manually. test "x$prefix" = xNONE && test "x$sysconfdir" = 'x${prefix}/etc' && sysconfdir='/etc' # The mstpd PID file should not be installed under /usr/ if mstpd itself is not # installed under /usr. If exec_prefix is empty and `./configure` was run # without specifying `--localstatedir=...`, then set the default localstatedir # appropriately. # There is no standard macro to override the default localstatedir, so just do # it manually. test "x$exec_prefix" = x && test "x$localstatedir" = 'x${prefix}/var' && localstatedir='/var' # If exec_prefix is empty, then the default libexecdir is normally /libexec/, # which is not a standard path. Therefore, if exec_prefix is empty and # `./configure` was run without specifying `--libexecdir=...`, set the default # localstatedir to /lib/ instead of /libexec/. # There is no standard macro to override the default libexecdir, so just do it # manually. test "x$exec_prefix" = x && test "x$libexecdir" = 'x${exec_prefix}/libexec' && libexecdir='/lib' AM_INIT_AUTOMAKE([foreign -Wall]) AC_CONFIG_MACRO_DIR([m4]) AC_PROG_CC m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) LT_INIT PKG_PROG_PKG_CONFIG # Optional building of examples AC_ARG_ENABLE([devel], [AC_HELP_STRING([--enable-devel], [build devel mode])]) AM_CONDITIONAL([ENABLE_DEVEL], [test "x$enable_devel" = "xyes"]) AC_ARG_ENABLE([install-ifupdown-scripts], [AC_HELP_STRING([--enable-install-ifupdown-scripts], [enable installation of ifupdown scripts])]) AM_CONDITIONAL([ENABLE_INSTALL_IFUPDOWN_SCRIPTS], [test "x$enable_install_ifupdown_scripts" = "xyes"]) AC_ARG_WITH([bashcompletiondir], [AS_HELP_STRING([--with-bashcompletiondir=DIR], [Directory for bash completion files. [default=${sysconfdir}/bash_completion.d]])],, [with_bashcompletiondir=default]) AS_IF([test "x$with_bashcompletiondir" = "xdefault"], [AC_SUBST([bashcompletiondir], [${sysconfdir}/bash_completion.d])]) AS_IF([test "x$with_bashcompletiondir" != "xdefault"], [AC_SUBST([bashcompletiondir], [$with_bashcompletiondir])]) AC_ARG_WITH([systemdunitdir], [AS_HELP_STRING([--with-systemunitdir=DIR], [Directory for systemd unit files.])],, [with_systemdunitdir=default]) AS_IF([test "x$with_systemdunitdir" = "xdefault"], [AC_SUBST([systemdunitdir], [$($PKG_CONFIG --variable=systemdsystemunitdir systemd 2> /dev/null)])]) AS_IF([test "x$with_systemdunitdir" != "xdefault"], [AC_SUBST([systemdunitdir], [$with_systemdunitdir])]) # Define PACKAGE_BUILD PACKAGE_BUILD=`git log --pretty=format:'%h' -n 1` AC_SUBST(PACKAGE_BUILD) AC_DEFINE_UNQUOTED(PACKAGE_BUILD, "$PACKAGE_BUILD", [Build revision obtained from git]) # Override PACKAGE_VERSION to include git tag PACKAGE_VERSION="${PACKAGE_VERSION}-${PACKAGE_BUILD}" AC_SUBST(PACKAGE_VERSION) AC_DEFINE_UNQUOTED(PACKAGE_VERSION, "$PACKAGE_VERSION", [Package version, including build number]) AC_SEARCH_LIBS([clock_gettime], [rt]) AC_CHECK_TYPES(struct timespec) AC_CHECK_FUNCS(clock_gettime) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT mstpd-mstpd-8995e5d/ctl_functions.h000066400000000000000000000401311516242366700174020ustar00rootroot00000000000000/***************************************************************************** Copyright (c) 2006 EMC Corporation. Copyright (c) 2011 Factor-SPE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. Authors: Srinivas Aji Authors: Vitalii Demianets ******************************************************************************/ #ifndef CTL_SOCKET_H #define CTL_SOCKET_H #include #include #include #include #include "mstp.h" struct ctl_msg_hdr { int cmd; int lin; int lout; int llog; int res; }; #define LOG_STRING_LEN 256 typedef struct _log_string { char buf[LOG_STRING_LEN]; } LogString; #define set_socket_address(sa, string) do{ \ struct sockaddr_un * tmp_sa = (sa); \ memset(tmp_sa, 0, sizeof(*tmp_sa)); \ tmp_sa->sun_family = AF_UNIX; \ strcpy(tmp_sa->sun_path + 1, (string)); \ }while(0) #define MSTP_SERVER_SOCK_NAME ".mstp_server" /* Commands sent from bridge-stp script need this flag */ #define RESPONSE_FIRST_HANDLE_LATER 0x10000 /* COMMANDS */ #define CTL_DECLARE(name) \ int CTL_ ## name name ## _ARGS /* get_cist_bridge_status */ #define CMD_CODE_get_cist_bridge_status 101 #define get_cist_bridge_status_ARGS (int br_index, CIST_BridgeStatus *status, \ char *root_port_name) struct get_cist_bridge_status_IN { int br_index; }; struct get_cist_bridge_status_OUT { CIST_BridgeStatus status; char root_port_name[IFNAMSIZ]; }; #define get_cist_bridge_status_COPY_IN ({ in->br_index = br_index; }) #define get_cist_bridge_status_COPY_OUT ({ *status = out->status; \ strncpy(root_port_name, out->root_port_name, IFNAMSIZ); }) #define get_cist_bridge_status_CALL (in->br_index, &out->status, \ out->root_port_name) CTL_DECLARE(get_cist_bridge_status); /* get_msti_bridge_status */ #define CMD_CODE_get_msti_bridge_status 102 #define get_msti_bridge_status_ARGS (int br_index, __u16 mstid, \ MSTI_BridgeStatus *status, \ char *root_port_name) struct get_msti_bridge_status_IN { int br_index; __u16 mstid; }; struct get_msti_bridge_status_OUT { MSTI_BridgeStatus status; char root_port_name[IFNAMSIZ]; }; #define get_msti_bridge_status_COPY_IN \ ({ in->br_index = br_index; in->mstid = mstid; }) #define get_msti_bridge_status_COPY_OUT ({ *status = out->status; \ strncpy(root_port_name, out->root_port_name, IFNAMSIZ); }) #define get_msti_bridge_status_CALL (in->br_index, in->mstid, &out->status, \ out->root_port_name) CTL_DECLARE(get_msti_bridge_status); /* set_cist_bridge_config */ #define CMD_CODE_set_cist_bridge_config 103 #define set_cist_bridge_config_ARGS (int br_index, CIST_BridgeConfig *cfg) struct set_cist_bridge_config_IN { int br_index; CIST_BridgeConfig cfg; }; struct set_cist_bridge_config_OUT { }; #define set_cist_bridge_config_COPY_IN \ ({ in->br_index = br_index; in->cfg = *cfg; }) #define set_cist_bridge_config_COPY_OUT ({ (void)0; }) #define set_cist_bridge_config_CALL (in->br_index, &in->cfg) CTL_DECLARE(set_cist_bridge_config); /* set_msti_bridge_config */ #define CMD_CODE_set_msti_bridge_config 104 #define set_msti_bridge_config_ARGS (int br_index, __u16 mstid, \ __u8 bridge_priority) struct set_msti_bridge_config_IN { int br_index; __u16 mstid; __u8 bridge_priority; }; struct set_msti_bridge_config_OUT { }; #define set_msti_bridge_config_COPY_IN \ ({ in->br_index = br_index; in->mstid = mstid; \ in->bridge_priority = bridge_priority; }) #define set_msti_bridge_config_COPY_OUT ({ (void)0; }) #define set_msti_bridge_config_CALL (in->br_index, in->mstid, \ in->bridge_priority) CTL_DECLARE(set_msti_bridge_config); /* get_cist_port_status */ #define CMD_CODE_get_cist_port_status 105 #define get_cist_port_status_ARGS (int br_index, int port_index, \ CIST_PortStatus *status) struct get_cist_port_status_IN { int br_index; int port_index; }; struct get_cist_port_status_OUT { CIST_PortStatus status; }; #define get_cist_port_status_COPY_IN \ ({ in->br_index = br_index; in->port_index = port_index; }) #define get_cist_port_status_COPY_OUT ({ *status = out->status; }) #define get_cist_port_status_CALL (in->br_index, in->port_index, &out->status) CTL_DECLARE(get_cist_port_status); /* get_msti_port_status */ #define CMD_CODE_get_msti_port_status 106 #define get_msti_port_status_ARGS (int br_index, int port_index, __u16 mstid, \ MSTI_PortStatus *status) struct get_msti_port_status_IN { int br_index; int port_index; __u16 mstid; }; struct get_msti_port_status_OUT { MSTI_PortStatus status; }; #define get_msti_port_status_COPY_IN \ ({ in->br_index = br_index; in->port_index = port_index; \ in->mstid = mstid; }) #define get_msti_port_status_COPY_OUT ({ *status = out->status; }) #define get_msti_port_status_CALL (in->br_index, in->port_index, in->mstid, \ &out->status) CTL_DECLARE(get_msti_port_status); /* set_cist_port_config */ #define CMD_CODE_set_cist_port_config 107 #define set_cist_port_config_ARGS (int br_index, int port_index, \ CIST_PortConfig *cfg) struct set_cist_port_config_IN { int br_index; int port_index; CIST_PortConfig cfg; }; struct set_cist_port_config_OUT { }; #define set_cist_port_config_COPY_IN \ ({ in->br_index = br_index; in->port_index = port_index; in->cfg = *cfg; }) #define set_cist_port_config_COPY_OUT ({ (void)0; }) #define set_cist_port_config_CALL (in->br_index, in->port_index, &in->cfg) CTL_DECLARE(set_cist_port_config); /* set_msti_port_config */ #define CMD_CODE_set_msti_port_config 108 #define set_msti_port_config_ARGS (int br_index, int port_index, __u16 mstid, \ MSTI_PortConfig *cfg) struct set_msti_port_config_IN { int br_index; int port_index; __u16 mstid; MSTI_PortConfig cfg; }; struct set_msti_port_config_OUT { }; #define set_msti_port_config_COPY_IN \ ({ in->br_index = br_index; in->port_index = port_index; \ in->mstid = mstid; in->cfg = *cfg; }) #define set_msti_port_config_COPY_OUT ({ (void)0; }) #define set_msti_port_config_CALL (in->br_index, in->port_index, in->mstid, \ &in->cfg) CTL_DECLARE(set_msti_port_config); /* port_mcheck */ #define CMD_CODE_port_mcheck 109 #define port_mcheck_ARGS (int br_index, int port_index) struct port_mcheck_IN { int br_index; int port_index; }; struct port_mcheck_OUT { }; #define port_mcheck_COPY_IN \ ({ in->br_index = br_index; in->port_index = port_index; }) #define port_mcheck_COPY_OUT ({ (void)0; }) #define port_mcheck_CALL (in->br_index, in->port_index) CTL_DECLARE(port_mcheck); /* set_debug_level */ #define CMD_CODE_set_debug_level 110 #define set_debug_level_ARGS (int level) struct set_debug_level_IN { int level; }; struct set_debug_level_OUT { }; #define set_debug_level_COPY_IN ({ in->level = level; }) #define set_debug_level_COPY_OUT ({ (void)0; }) #define set_debug_level_CALL (in->level) CTL_DECLARE(set_debug_level); /* get_mstilist */ #define CMD_CODE_get_mstilist 111 #define get_mstilist_ARGS (int br_index, int *num_mstis, __u16 *mstids) struct get_mstilist_IN { int br_index; }; struct get_mstilist_OUT { int num_mstis; __u16 mstids[MAX_IMPLEMENTATION_MSTIS + 1]; /* +1 - for the CIST */ }; #define get_mstilist_COPY_IN \ ({ in->br_index = br_index; }) #define get_mstilist_COPY_OUT ({ *num_mstis = out->num_mstis; \ memcpy(mstids, out->mstids, (*num_mstis) * sizeof(out->mstids[0])); }) #define get_mstilist_CALL (in->br_index, &out->num_mstis, out->mstids) CTL_DECLARE(get_mstilist); /* create_msti */ #define CMD_CODE_create_msti 112 #define create_msti_ARGS (int br_index, __u16 mstid) struct create_msti_IN { int br_index; __u16 mstid; }; struct create_msti_OUT { }; #define create_msti_COPY_IN \ ({ in->br_index = br_index; in->mstid = mstid; }) #define create_msti_COPY_OUT ({ (void)0; }) #define create_msti_CALL (in->br_index, in->mstid) CTL_DECLARE(create_msti); /* delete_msti */ #define CMD_CODE_delete_msti 113 #define delete_msti_ARGS (int br_index, __u16 mstid) struct delete_msti_IN { int br_index; __u16 mstid; }; struct delete_msti_OUT { }; #define delete_msti_COPY_IN \ ({ in->br_index = br_index; in->mstid = mstid; }) #define delete_msti_COPY_OUT ({ (void)0; }) #define delete_msti_CALL (in->br_index, in->mstid) CTL_DECLARE(delete_msti); /* get_mstconfid */ #define CMD_CODE_get_mstconfid 114 #define get_mstconfid_ARGS (int br_index, mst_configuration_identifier_t *cfg) struct get_mstconfid_IN { int br_index; }; struct get_mstconfid_OUT { mst_configuration_identifier_t cfg; }; #define get_mstconfid_COPY_IN ({ in->br_index = br_index; }) #define get_mstconfid_COPY_OUT ({ *cfg = out->cfg; }) #define get_mstconfid_CALL (in->br_index, &out->cfg) CTL_DECLARE(get_mstconfid); /* set_mstconfid */ #define CMD_CODE_set_mstconfid 115 #define set_mstconfid_ARGS (int br_index, __u16 revision, __u8 *name) struct set_mstconfid_IN { int br_index; __u16 revision; __u8 name[CONFIGURATION_NAME_LEN]; }; struct set_mstconfid_OUT { }; #define set_mstconfid_COPY_IN ({ in->br_index = br_index; \ in->revision = revision; strncpy((char *)in->name, (char *)name, sizeof(in->name)); }) #define set_mstconfid_COPY_OUT ({ (void)0; }) #define set_mstconfid_CALL (in->br_index, in->revision, in->name) CTL_DECLARE(set_mstconfid); /* get_vids2fids */ #define CMD_CODE_get_vids2fids 116 #define get_vids2fids_ARGS (int br_index, __u16 *vids2fids) struct get_vids2fids_IN { int br_index; }; struct get_vids2fids_OUT { __u16 vids2fids[MAX_VID + 1]; }; #define get_vids2fids_COPY_IN ({ in->br_index = br_index; }) #define get_vids2fids_COPY_OUT ({ \ memcpy(vids2fids, out->vids2fids, sizeof(out->vids2fids)); }) #define get_vids2fids_CALL (in->br_index, out->vids2fids) CTL_DECLARE(get_vids2fids); /* get_fids2mstids */ #define CMD_CODE_get_fids2mstids 117 #define get_fids2mstids_ARGS (int br_index, __u16 *fids2mstids) struct get_fids2mstids_IN { int br_index; }; struct get_fids2mstids_OUT { __u16 fids2mstids[MAX_FID + 1]; }; #define get_fids2mstids_COPY_IN ({ in->br_index = br_index; }) #define get_fids2mstids_COPY_OUT ({ \ memcpy(fids2mstids, out->fids2mstids, sizeof(out->fids2mstids)); }) #define get_fids2mstids_CALL (in->br_index, out->fids2mstids) CTL_DECLARE(get_fids2mstids); /* set_vid2fid */ #define CMD_CODE_set_vid2fid 118 #define set_vid2fid_ARGS (int br_index, __u16 vid, __u16 fid) struct set_vid2fid_IN { int br_index; __u16 vid, fid; }; struct set_vid2fid_OUT { }; #define set_vid2fid_COPY_IN ({ in->br_index = br_index; in->vid = vid; \ in->fid = fid; }) #define set_vid2fid_COPY_OUT ({ (void)0; }) #define set_vid2fid_CALL (in->br_index, in->vid, in->fid) CTL_DECLARE(set_vid2fid); /* set_fid2mstid */ #define CMD_CODE_set_fid2mstid 119 #define set_fid2mstid_ARGS (int br_index, __u16 fid, __u16 mstid) struct set_fid2mstid_IN { int br_index; __u16 fid, mstid; }; struct set_fid2mstid_OUT { }; #define set_fid2mstid_COPY_IN ({ in->br_index = br_index; in->fid = fid; \ in->mstid = mstid; }) #define set_fid2mstid_COPY_OUT ({ (void)0; }) #define set_fid2mstid_CALL (in->br_index, in->fid, in->mstid) CTL_DECLARE(set_fid2mstid); /* set_vids2fids */ #define CMD_CODE_set_vids2fids 120 #define set_vids2fids_ARGS (int br_index, __u16 *vids2fids) struct set_vids2fids_IN { int br_index; __u16 vids2fids[MAX_VID + 1]; }; struct set_vids2fids_OUT { }; #define set_vids2fids_COPY_IN ({ in->br_index = br_index; \ memcpy(in->vids2fids, vids2fids, sizeof(in->vids2fids)); }) #define set_vids2fids_COPY_OUT ({ (void)0; }) #define set_vids2fids_CALL (in->br_index, in->vids2fids) CTL_DECLARE(set_vids2fids); /* set_fids2mstids */ #define CMD_CODE_set_fids2mstids 121 #define set_fids2mstids_ARGS (int br_index, __u16 *fids2mstids) struct set_fids2mstids_IN { int br_index; __u16 fids2mstids[MAX_FID + 1]; }; struct set_fids2mstids_OUT { }; #define set_fids2mstids_COPY_IN ({ in->br_index = br_index; \ memcpy(in->fids2mstids, fids2mstids, sizeof(in->fids2mstids)); }) #define set_fids2mstids_COPY_OUT ({ (void)0; }) #define set_fids2mstids_CALL (in->br_index, in->fids2mstids) CTL_DECLARE(set_fids2mstids); /* add bridges */ #define CMD_CODE_add_bridges (122 | RESPONSE_FIRST_HANDLE_LATER) #define add_bridges_ARGS (int *br_array, int* *ifaces_lists) CTL_DECLARE(add_bridges); /* delete bridges */ #define CMD_CODE_del_bridges (123 | RESPONSE_FIRST_HANDLE_LATER) #define del_bridges_ARGS (int *br_array) CTL_DECLARE(del_bridges); /* General case part in ctl command server switch */ #define SERVER_MESSAGE_CASE(name) \ case CMD_CODE_ ## name : do \ { \ struct name ## _IN in0, *in = &in0; \ struct name ## _OUT out0, *out = &out0; \ if(sizeof(*in) != lin || sizeof(*out) != lout) \ { \ LOG("Bad sizes lin %d != %zd or lout %d != %zd", \ lin, sizeof(*in), lout, sizeof(*out)); \ return -1; \ } \ memcpy(in, inbuf, lin); \ memset(out, 0, lout); \ int r = CTL_ ## name name ## _CALL; \ if(r) \ return r; \ if(outbuf) \ memcpy(outbuf, out, lout); \ return r; \ }while(0) /* Wraper for the control functions in the control command client */ #define CLIENT_SIDE_FUNCTION(name) \ CTL_DECLARE(name) \ { \ struct name ## _IN in0, *in = &in0; \ struct name ## _OUT out0, *out = &out0; \ name ## _COPY_IN; \ int res = 0; \ LogString log = { .buf = "" }; \ int r = send_ctl_message(CMD_CODE_ ## name, in, sizeof(*in), \ out, sizeof(*out), &log, &res); \ if(r || res) \ LOG("Got return code %d, %d\n%s", r, res, log.buf); \ if(r) \ return r; \ if(res) \ return res; \ name ## _COPY_OUT; \ return 0; \ } #endif /* CTL_SOCKET_H */ mstpd-mstpd-8995e5d/ctl_main.c000066400000000000000000002576331516242366700163320ustar00rootroot00000000000000/***************************************************************************** Copyright (c) 2011 Factor-SPE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. Authors: Vitalii Demianets ******************************************************************************/ #include #include #include #include #include #include #include #include "ctl_socket_client.h" #include "log.h" static int get_index_die(const char *ifname, const char *doc, bool die) { int r = if_nametoindex(ifname); if(0 == r) { fprintf(stderr, "Can't find index for %s %s. Not a valid interface.\n", doc, ifname); if(die) exit(1); return -1; } return r; } static inline int get_index(const char *ifname, const char *doc) { return get_index_die(ifname, doc, true); } static inline int get_id(const char *str, const char *doc, unsigned int max_id) { int id = strtol(str, NULL, 10); if((0 > id) || (max_id < id) || ((0 == id) && ('0' != str[0])) ) { fprintf(stderr, "Bad %s %s\n", doc, str); return -1; } return id; } /* Supported output formats */ typedef enum { FORMAT_PLAIN, /* plain text (default) */ FORMAT_JSON /* JSON */ } format_id_t; /* Default output format */ static format_id_t format = FORMAT_PLAIN; static inline void do_arraystart_fmt(void) { if (FORMAT_JSON == format) printf("["); } static inline void do_arrayend_fmt(void) { if (FORMAT_JSON == format) printf("]"); } static inline void do_arraynext_fmt(void) { if (FORMAT_JSON == format) printf(","); } #define GET_NUM_FROM_PRIO(p) (__be16_to_cpu(p) & 0x0FFF) #define BR_ID_FMT "%01hhX.%03hX.%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX" #define BR_ID_ARGS(x) ((GET_PRIORITY_FROM_IDENTIFIER(x) >> 4) & 0x0F), \ GET_NUM_FROM_PRIO((x).s.priority), \ x.s.mac_address[0], x.s.mac_address[1], x.s.mac_address[2], \ x.s.mac_address[3], x.s.mac_address[4], x.s.mac_address[5] #define PRT_ID_FMT "%01hhX.%03hX" #define PRT_ID_ARGS(x) ((GET_PRIORITY_FROM_IDENTIFIER(x) >> 4) & 0x0F), \ GET_NUM_FROM_PRIO(x) #define BOOL_STR(x) ((x) ? "yes" : "no") #define PROTO_VERS_STR(x) ((protoRSTP == (x)) ? "rstp" : \ ((protoMSTP <= (x)) ? "mstp" : "stp")) typedef enum { PARAM_NULL = 0, /* bridge params */ PARAM_ENABLED, PARAM_BRID, PARAM_DSGNROOT, PARAM_REGNROOT, PARAM_ROOTPORT, PARAM_PATHCOST, PARAM_INTPATHCOST, PARAM_MAXAGE, PARAM_BRMAXAGE, PARAM_FWDDELAY, PARAM_BRFWDDELAY, PARAM_TXHOLDCNT, PARAM_MAXHOPS, PARAM_BRHELLO, PARAM_BRAGEING, PARAM_FORCEPROTVERS, PARAM_TOPCHNGTIME, PARAM_TOPCHNGCNT, PARAM_TOPCHNGSTATE, /* port params */ PARAM_ROLE, PARAM_STATE, PARAM_PORTID, PARAM_EXTPORTCOST, PARAM_ADMINEXTCOST, PARAM_INTPORTCOST, PARAM_ADMININTCOST, PARAM_DSGNEXTCOST, PARAM_DSGNRROOT, PARAM_DSGNINTCOST, PARAM_DSGNBR, PARAM_DSGNPORT, PARAM_ADMINEDGEPORT, PARAM_AUTOEDGEPORT, PARAM_OPEREDGEPORT, PARAM_TOPCHNGACK, PARAM_P2P, PARAM_ADMINP2P, PARAM_RESTRROLE, PARAM_RESTRTCN, PARAM_PORTHELLOTIME, PARAM_DISPUTED, PARAM_BPDUGUARDPORT, PARAM_BPDUGUARDERROR, PARAM_BPDUFILTERPORT, PARAM_NETWORKPORT, PARAM_BA_INCONSISTENT, PARAM_NUMTXBPDU, PARAM_NUMRXBPDU, PARAM_NUMTXTCN, PARAM_NUMRXTCN, PARAM_NUMTRANSFWD, PARAM_NUMTRANSBLK, PARAM_NUMBPDUFILTERED, PARAM_RCVDBPDU, PARAM_RCVDSTP, PARAM_RCVDRSTP, PARAM_SENDRSTP, PARAM_RCVDTCACK, PARAM_RCVDTCN, } param_id_t; typedef struct { param_id_t id; char *str; } cmd_param_t; static const cmd_param_t cist_bridge_params[] = { { PARAM_ENABLED, "enabled" }, { PARAM_BRID, "bridge-id" }, { PARAM_DSGNROOT, "designated-root" }, { PARAM_REGNROOT, "regional-root" }, { PARAM_ROOTPORT, "root-port" }, { PARAM_PATHCOST, "path-cost" }, { PARAM_INTPATHCOST, "internal-path-cost" }, { PARAM_MAXAGE, "max-age" }, { PARAM_BRMAXAGE, "bridge-max-age" }, { PARAM_FWDDELAY, "forward-delay" }, { PARAM_BRFWDDELAY, "bridge-forward-delay" }, { PARAM_TXHOLDCNT, "tx-hold-count" }, { PARAM_MAXHOPS, "max-hops" }, { PARAM_BRHELLO, "hello-time" }, { PARAM_BRAGEING, "ageing-time" }, { PARAM_FORCEPROTVERS,"force-protocol-version" }, { PARAM_TOPCHNGTIME, "time-since-topology-change" }, { PARAM_TOPCHNGCNT, "topology-change-count" }, { PARAM_TOPCHNGSTATE, "topology-change" }, }; static int do_showbridge_fmt_plain(const CIST_BridgeStatus *s, const char *br_name, const char *root_port_name, param_id_t param_id) { unsigned int root_portno; switch(param_id) { case PARAM_NULL: printf("%s CIST info\n", br_name); printf(" enabled %s\n", BOOL_STR(s->enabled)); printf(" bridge id "BR_ID_FMT"\n", BR_ID_ARGS(s->bridge_id)); printf(" designated root "BR_ID_FMT"\n", BR_ID_ARGS(s->designated_root)); printf(" regional root "BR_ID_FMT"\n", BR_ID_ARGS(s->regional_root)); printf(" root port "); if(0 != (root_portno = GET_NUM_FROM_PRIO(s->root_port_id))) printf("%s (#%u)\n", root_port_name, root_portno); else printf("none\n"); printf(" path cost %-10u ", s->root_path_cost); printf("internal path cost %u\n", s->internal_path_cost); printf(" max age %-10hhu ", s->root_max_age); printf("bridge max age %hhu\n", s->bridge_max_age); printf(" forward delay %-10hhu ", s->root_forward_delay); printf("bridge forward delay %hhu\n", s->bridge_forward_delay); printf(" tx hold count %-10u ", s->tx_hold_count); printf("max hops %hhu\n", s->max_hops); printf(" hello time %-10u ", s->bridge_hello_time); printf("ageing time %u\n", s->Ageing_Time); printf(" force protocol version %s\n", PROTO_VERS_STR(s->protocol_version)); printf(" time since topology change %u\n", s->time_since_topology_change); printf(" topology change count %u\n", s->topology_change_count); printf(" topology change %s\n", BOOL_STR(s->topology_change)); printf(" topology change port %s\n", s->topology_change_port); printf(" last topology change port %s\n", s->last_topology_change_port); break; case PARAM_ENABLED: printf("%s\n", BOOL_STR(s->enabled)); break; case PARAM_BRID: printf(BR_ID_FMT"\n", BR_ID_ARGS(s->bridge_id)); break; case PARAM_DSGNROOT: printf(BR_ID_FMT"\n", BR_ID_ARGS(s->designated_root)); break; case PARAM_REGNROOT: printf(BR_ID_FMT"\n", BR_ID_ARGS(s->regional_root)); break; case PARAM_ROOTPORT: if(0 != (root_portno = GET_NUM_FROM_PRIO(s->root_port_id))) printf("%s\n", root_port_name); else printf("\n"); break; case PARAM_PATHCOST: printf("%u\n", s->root_path_cost); break; case PARAM_INTPATHCOST: printf("%u\n", s->internal_path_cost); break; case PARAM_MAXAGE: printf("%hhu\n", s->root_max_age); break; case PARAM_BRMAXAGE: printf("%hhu\n", s->bridge_max_age); break; case PARAM_FWDDELAY: printf("%hhu\n", s->root_forward_delay); break; case PARAM_BRFWDDELAY: printf("%hhu\n", s->bridge_forward_delay); break; case PARAM_TXHOLDCNT: printf("%u\n", s->tx_hold_count); break; case PARAM_MAXHOPS: printf("%hhu\n", s->max_hops); break; case PARAM_BRHELLO: printf("%hhu\n", s->bridge_hello_time); break; case PARAM_BRAGEING: printf("%u\n", s->Ageing_Time); break; case PARAM_FORCEPROTVERS: printf("%s\n", PROTO_VERS_STR(s->protocol_version)); break; case PARAM_TOPCHNGTIME: printf("%u\n", s->time_since_topology_change); break; case PARAM_TOPCHNGCNT: printf("%u\n", s->topology_change_count); break; case PARAM_TOPCHNGSTATE: printf("%s\n", BOOL_STR(s->topology_change)); break; default: return -2; /* -2 = unknown param */ } return 0; } static int do_showbridge_fmt_json(const CIST_BridgeStatus *s, const char *br_name, const char *root_port_name, param_id_t param_id) { unsigned int root_portno; switch(param_id) { case PARAM_NULL: printf("{"); printf("\"bridge\":\"%s\",", br_name); printf("\"enabled\":\"%s\",", BOOL_STR(s->enabled)); printf("\"bridge-id\":\""BR_ID_FMT"\",", BR_ID_ARGS(s->bridge_id)); printf("\"designated-root\":\""BR_ID_FMT"\",", BR_ID_ARGS(s->designated_root)); printf("\"regional-root\":\""BR_ID_FMT"\",", BR_ID_ARGS(s->regional_root)); if(0 != (root_portno = GET_NUM_FROM_PRIO(s->root_port_id))) printf("\"root-port\":\"%s (#%u)\",", root_port_name, root_portno); else printf("\"root-port\":\"\","); printf("\"path-cost\":\"%u\",", s->root_path_cost); printf("\"internal-path-cost\":\"%u\",", s->internal_path_cost); printf("\"max-age\":\"%u\",", s->root_max_age); printf("\"bridge-max-age\":\"%u\",", s->bridge_max_age); printf("\"forward-delay\":\"%hhu\",", s->root_forward_delay); printf("\"bridge-forward-delay\":\"%hhu\",", s->bridge_forward_delay); printf("\"tx-hold-count\":\"%u\",", s->tx_hold_count); printf("\"max-hops\":\"%hhu\",", s->max_hops); printf("\"hello-time\":\"%u\",", s->bridge_hello_time); printf("\"ageing-time\":\"%u\",", s->Ageing_Time); printf("\"force-protocol-version\":\"%s\",", PROTO_VERS_STR(s->protocol_version)); printf("\"time-since-topology-change\":\"%u\",", s->time_since_topology_change); printf("\"topology-change-count\":\"%u\",", s->topology_change_count); printf("\"topology-change\":\"%s\",", BOOL_STR(s->topology_change)); printf("\"topology-change-port\":\"%s\",", s->topology_change_port); printf("\"last-topology-change-port\":\"%s\"", s->last_topology_change_port); printf("}"); break; case PARAM_ENABLED: case PARAM_BRID: case PARAM_DSGNROOT: case PARAM_REGNROOT: case PARAM_ROOTPORT: case PARAM_PATHCOST: case PARAM_INTPATHCOST: case PARAM_MAXAGE: case PARAM_BRMAXAGE: case PARAM_FWDDELAY: case PARAM_BRFWDDELAY: case PARAM_TXHOLDCNT: case PARAM_MAXHOPS: case PARAM_BRHELLO: case PARAM_BRAGEING: case PARAM_FORCEPROTVERS: case PARAM_TOPCHNGTIME: case PARAM_TOPCHNGCNT: case PARAM_TOPCHNGSTATE: /* Output individual parameters for the JSON format as plain text in quotes */ printf("\""); int err = do_showbridge_fmt_plain(s, br_name, root_port_name, param_id); printf("\""); return err; default: return -2; /* -2 = unknown param */ } return 0; } static int do_showbridge(const char *br_name, param_id_t param_id) { CIST_BridgeStatus s; char root_port_name[IFNAMSIZ]; int br_index = get_index_die(br_name, "bridge", false); if(0 > br_index) return br_index; if(CTL_get_cist_bridge_status(br_index, &s, root_port_name)) return -1; switch(format) { case FORMAT_PLAIN: return do_showbridge_fmt_plain(&s, br_name, root_port_name, param_id); case FORMAT_JSON: return do_showbridge_fmt_json(&s, br_name, root_port_name, param_id); default: return -3; /* -3 = unsupported or unknown format */ } } #define SYSFS_PATH_MAX 256 #ifndef SYSFS_CLASS_NET #define SYSFS_CLASS_NET "/sys/class/net" #endif static int isbridge(const struct dirent *entry) { char path[SYSFS_PATH_MAX]; int save_errno; bool result; struct stat st; /* strlen(SYSFS_CLASS_NET) + strlen("/%.230s/bridge") must be < SYSFS_PATH_MAX to prevent string truncation ; gcc7's fortify headers complain about that */ snprintf(path, SYSFS_PATH_MAX, SYSFS_CLASS_NET "/%.230s/bridge", entry->d_name); save_errno = errno; result = (0 == stat(path, &st)) && S_ISDIR(st.st_mode); errno = save_errno; return result; } static inline int get_bridge_list(struct dirent ***namelist) { return scandir(SYSFS_CLASS_NET, namelist, isbridge, versionsort); } static int cmd_showbridge(int argc, char *const *argv) { int i, count = 0; int r = 0; struct dirent **namelist; param_id_t param_id = PARAM_NULL; if(1 < argc) { count = argc - 1; if(1 < count) { /* check if last argument is known parameter name */ for(i = 0; i < COUNT_OF(cist_bridge_params); ++i) { if(0 == strcmp(argv[count], cist_bridge_params[i].str)) { param_id = cist_bridge_params[i].id; --count; break; } } } } else { count = get_bridge_list(&namelist); if(0 > count) { fprintf(stderr, "Error getting list of all bridges\n"); return -1; } } do_arraystart_fmt(); for(i = 0; i < count; ++i) { const char *name; if(1 < argc) name = argv[i + 1]; else name = namelist[i]->d_name; if(i) do_arraynext_fmt(); int err = do_showbridge(name, param_id); if(err) r = err; } do_arrayend_fmt(); if(1 >= argc) { for(i = 0; i < count; ++i) free(namelist[i]); free(namelist); } return r; } static int do_showtree_fmt_plain(const MSTI_BridgeStatus *s, const char *br_name, int mstid, const char *root_port_name) { unsigned int root_portno; printf("%s MSTI %hu info\n", br_name, (unsigned short)mstid); printf(" bridge id "BR_ID_FMT"\n", BR_ID_ARGS(s->bridge_id)); printf(" regional root "BR_ID_FMT"\n", BR_ID_ARGS(s->regional_root)); printf(" root port "); if(0 != (root_portno = GET_NUM_FROM_PRIO(s->root_port_id))) printf("%s (#%u)\n", root_port_name, root_portno); else printf("none\n"); printf(" internal path cost %u\n", s->internal_path_cost); printf(" time since topology change %u\n", s->time_since_topology_change); printf(" topology change count %u\n", s->topology_change_count); printf(" topology change %s\n", BOOL_STR(s->topology_change)); printf(" topology change port %s\n", s->topology_change_port); printf(" last topology change port %s\n", s->last_topology_change_port); return 0; } static int do_showtree_fmt_json(const MSTI_BridgeStatus *s, const char *br_name, int mstid, const char *root_port_name) { unsigned int root_portno; printf("{"); printf("\"bridge\":\"%s\",", br_name); printf("\"mstid\":\"%hu\",", (unsigned short)mstid); printf("\"bridge-id\":\""BR_ID_FMT"\",", BR_ID_ARGS(s->bridge_id)); printf("\"regional-root\":\""BR_ID_FMT"\",", BR_ID_ARGS(s->regional_root)); printf("\"root-port\":"); if(0 != (root_portno = GET_NUM_FROM_PRIO(s->root_port_id))) printf("\"%s (#%u)\",", root_port_name, root_portno); else printf("\"none\","); printf("\"internal-path-cost\":\"%u\",", s->internal_path_cost); printf("\"time-since-topology-change\":\"%u\",", s->time_since_topology_change); printf("\"topology-change-count\":\"%u\",", s->topology_change_count); printf("\"topology-change\":\"%s\",", BOOL_STR(s->topology_change)); printf("\"topology-change-port\":\"%s\",", s->topology_change_port); printf("\"last-topology-change-port\":\"%s\"", s->last_topology_change_port); printf("}"); return 0; } static int cmd_showtree(int argc, char *const *argv) { MSTI_BridgeStatus s; char root_port_name[IFNAMSIZ]; int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int mstid = get_id(argv[2], "mstid", MAX_MSTID); if(0 > mstid) return mstid; if(CTL_get_msti_bridge_status(br_index, mstid, &s, root_port_name)) return -1; switch(format) { case FORMAT_PLAIN: return do_showtree_fmt_plain(&s, argv[1], mstid, root_port_name); case FORMAT_JSON: return do_showtree_fmt_json(&s, argv[1], mstid, root_port_name); default: return -3; /* -3 = unsupported or unknown format */ } } #define STATE_STR(_state) \ ({ \ int _s = _state; \ char *_str = "unknown"; \ switch(_s) \ { \ case BR_STATE_DISABLED: \ case BR_STATE_BLOCKING: \ case BR_STATE_LISTENING: _str = "discarding"; break; \ case BR_STATE_LEARNING: _str = "learning"; break; \ case BR_STATE_FORWARDING:_str = "forwarding"; break; \ } \ _str; \ }) #define SHORT_STATE_STR(_state) \ ({ \ int _s = _state; \ char *_str = "unkn"; \ switch(_s) \ { \ case BR_STATE_DISABLED: \ case BR_STATE_BLOCKING: \ case BR_STATE_LISTENING: _str = "disc"; break; \ case BR_STATE_LEARNING: _str = "lear"; break; \ case BR_STATE_FORWARDING:_str = "forw"; break; \ } \ _str; \ }) #define ADMIN_P2P_STR(_state) \ ({ \ admin_p2p_t _s = _state; \ char *_str = "unkn"; \ switch(_s) \ { \ case p2pForceFalse:_str = "no"; break; \ case p2pForceTrue: _str = "yes"; break; \ case p2pAuto: _str = "auto"; break; \ } \ _str; \ }) #define ROLE_STR(_role) \ ({ \ port_role_t _r = _role; \ char *_str = "Unknown"; \ switch(_r) \ { \ case roleRoot: _str = "Root"; break; \ case roleDesignated:_str = "Designated"; break; \ case roleAlternate: _str = "Alternate"; break; \ case roleBackup: _str = "Backup"; break; \ case roleMaster: _str = "Master"; break; \ case roleDisabled: _str = "Disabled"; break; \ } \ _str; \ }) #define SHORT_ROLE_STR(_role) \ ({ \ port_role_t _r = _role; \ char *_str = "Unkn"; \ switch(_r) \ { \ case roleRoot: _str = "Root"; break; \ case roleDesignated:_str = "Desg"; break; \ case roleAlternate: _str = "Altn"; break; \ case roleBackup: _str = "Back"; break; \ case roleMaster: _str = "Mstr"; break; \ case roleDisabled: _str = "Disa"; break; \ } \ _str; \ }) static const cmd_param_t cist_port_params[] = { { PARAM_ENABLED, "enabled" }, { PARAM_ROLE, "role" }, { PARAM_STATE, "state" }, { PARAM_PORTID, "port-id" }, { PARAM_EXTPORTCOST, "external-port-cost" }, { PARAM_ADMINEXTCOST, "admin-external-cost" }, { PARAM_INTPORTCOST, "internal-port-cost" }, { PARAM_ADMININTCOST, "admin-internal-cost" }, { PARAM_DSGNROOT, "designated-root" }, { PARAM_DSGNEXTCOST, "dsgn-external-cost" }, { PARAM_DSGNRROOT, "dsgn-regional-root" }, { PARAM_DSGNINTCOST, "dsgn-internal-cost" }, { PARAM_DSGNBR, "designated-bridge" }, { PARAM_DSGNPORT, "designated-port" }, { PARAM_ADMINEDGEPORT, "admin-edge-port" }, { PARAM_AUTOEDGEPORT, "auto-edge-port" }, { PARAM_OPEREDGEPORT, "oper-edge-port" }, { PARAM_TOPCHNGACK, "topology-change-ack" }, { PARAM_P2P, "point-to-point" }, { PARAM_ADMINP2P, "admin-point-to-point" }, { PARAM_RESTRROLE, "restricted-role" }, { PARAM_RESTRTCN, "restricted-TCN" }, { PARAM_PORTHELLOTIME, "port-hello-time" }, { PARAM_DISPUTED, "disputed" }, { PARAM_BPDUGUARDPORT, "bpdu-guard-port" }, { PARAM_BPDUGUARDERROR, "bpdu-guard-error" }, { PARAM_BPDUFILTERPORT, "bpdu-filter-port" }, { PARAM_NETWORKPORT, "network-port" }, { PARAM_BA_INCONSISTENT,"ba-inconsistent" }, { PARAM_NUMTXBPDU, "num-tx-bpdu" }, { PARAM_NUMRXBPDU, "num-rx-bpdu" }, { PARAM_NUMTXTCN, "num-tx-tcn" }, { PARAM_NUMRXTCN, "num-rx-tcn" }, { PARAM_NUMTRANSFWD, "num-transition-fwd" }, { PARAM_NUMTRANSBLK, "num-transition-blk" }, { PARAM_NUMBPDUFILTERED,"num-rx-bpdu-filtered" }, { PARAM_RCVDBPDU, "received-bpdu" }, { PARAM_RCVDSTP, "received-stp" }, { PARAM_RCVDRSTP, "received-rstp" }, { PARAM_SENDRSTP, "send-rstp" }, { PARAM_RCVDTCACK, "received-tc-ack" }, { PARAM_RCVDTCN, "received-tcn" }, }; static int detail = 0; static int do_showport_fmt_plain(const CIST_PortStatus *s, const char *bridge_name, const char *port_name, param_id_t param_id) { switch(param_id) { case PARAM_NULL: if(detail) { printf("%s:%s CIST info\n", bridge_name, port_name); printf(" enabled %-23s ", BOOL_STR(s->enabled)); printf("role %s\n", ROLE_STR(s->role)); printf(" port id "PRT_ID_FMT" ", PRT_ID_ARGS(s->port_id)); printf("state %s\n", STATE_STR(s->state)); printf(" external port cost %-23u ", s->external_port_path_cost); printf("admin external cost %u\n", s->admin_external_port_path_cost); printf(" internal port cost %-23u ", s->internal_port_path_cost); printf("admin internal cost %u\n", s->admin_internal_port_path_cost); printf(" designated root "BR_ID_FMT" ", BR_ID_ARGS(s->designated_root)); printf("dsgn external cost %u\n", s->designated_external_cost); printf(" dsgn regional root "BR_ID_FMT" ", BR_ID_ARGS(s->designated_regional_root)); printf("dsgn internal cost %u\n", s->designated_internal_cost); printf(" designated bridge "BR_ID_FMT" ", BR_ID_ARGS(s->designated_bridge)); printf("designated port "PRT_ID_FMT"\n", PRT_ID_ARGS(s->designated_port)); printf(" admin edge port %-23s ", BOOL_STR(s->admin_edge_port)); printf("auto edge port %s\n", BOOL_STR(s->auto_edge_port)); printf(" oper edge port %-23s ", BOOL_STR(s->oper_edge_port)); printf("topology change ack %s\n", BOOL_STR(s->tc_ack)); printf(" point-to-point %-23s ", BOOL_STR(s->oper_p2p)); printf("admin point-to-point %s\n", ADMIN_P2P_STR(s->admin_p2p)); printf(" restricted role %-23s ", BOOL_STR(s->restricted_role)); printf("restricted TCN %s\n", BOOL_STR(s->restricted_tcn)); printf(" port hello time %-23hhu ", s->port_hello_time); printf("disputed %s\n", BOOL_STR(s->disputed)); printf(" bpdu guard port %-23s ", BOOL_STR(s->bpdu_guard_port)); printf("bpdu guard error %s\n", BOOL_STR(s->bpdu_guard_error)); printf(" network port %-23s ", BOOL_STR(s->network_port)); printf("BA inconsistent %s\n", BOOL_STR(s->ba_inconsistent)); printf(" bpdu filter port %-23s ", BOOL_STR(s->bpdu_filter_port)); printf("Num RX BPDU Filtered %u\n", s->num_rx_bpdu_filtered); printf(" Num TX BPDU %-23u ", s->num_tx_bpdu); printf("Num TX TCN %u\n", s->num_tx_tcn); printf(" Num RX BPDU %-23u ", s->num_rx_bpdu); printf("Num RX TCN %u\n", s->num_rx_tcn); printf(" Num Transition FWD %-23u ", s->num_trans_fwd); printf("Num Transition BLK %u\n", s->num_trans_blk); printf(" Rcvd BPDU %-23s ", BOOL_STR(s->rcvdBpdu)); printf("Rcvd STP %s\n", BOOL_STR(s->rcvdSTP)); printf(" Rcvd RSTP %-23s ", BOOL_STR(s->rcvdRSTP)); printf("Send RSTP %s\n", BOOL_STR(s->sendRSTP)); printf(" Rcvd TC Ack %-23s ", BOOL_STR(s->rcvdTcAck)); printf("Rcvd TCN %s\n", BOOL_STR(s->rcvdTcn)); } else { printf("%c%c %-5s "PRT_ID_FMT" %4s "BR_ID_FMT" "BR_ID_FMT" " PRT_ID_FMT" %s\n", (s->oper_p2p) ? ' ' : '*', (s->oper_edge_port) ? 'E' : ' ', port_name, PRT_ID_ARGS(s->port_id), s->enabled ? SHORT_STATE_STR(s->state) : "down", BR_ID_ARGS(s->designated_root), BR_ID_ARGS(s->designated_bridge), PRT_ID_ARGS(s->designated_port), SHORT_ROLE_STR(s->role)); } break; case PARAM_ENABLED: printf("%s\n", BOOL_STR(s->enabled)); break; case PARAM_ROLE: printf("%s\n", ROLE_STR(s->role)); break; case PARAM_STATE: printf("%s\n", STATE_STR(s->state)); break; case PARAM_PORTID: printf(PRT_ID_FMT"\n", PRT_ID_ARGS(s->port_id)); break; case PARAM_EXTPORTCOST: printf("%u\n", s->external_port_path_cost); break; case PARAM_ADMINEXTCOST: printf("%u\n", s->admin_external_port_path_cost); break; case PARAM_INTPORTCOST: printf("%u\n", s->internal_port_path_cost); break; case PARAM_ADMININTCOST: printf("%u\n", s->admin_internal_port_path_cost); break; case PARAM_DSGNROOT: printf(BR_ID_FMT"\n", BR_ID_ARGS(s->designated_root)); break; case PARAM_DSGNEXTCOST: printf("%u\n", s->designated_external_cost); break; case PARAM_DSGNRROOT: printf(BR_ID_FMT"\n", BR_ID_ARGS(s->designated_regional_root)); break; case PARAM_DSGNINTCOST: printf("%u\n", s->designated_internal_cost); break; case PARAM_DSGNBR: printf(BR_ID_FMT"\n", BR_ID_ARGS(s->designated_bridge)); break; case PARAM_DSGNPORT: printf(PRT_ID_FMT"\n", PRT_ID_ARGS(s->designated_port)); break; case PARAM_ADMINEDGEPORT: printf("%s\n", BOOL_STR(s->admin_edge_port)); break; case PARAM_AUTOEDGEPORT: printf("%s\n", BOOL_STR(s->auto_edge_port)); break; case PARAM_OPEREDGEPORT: printf("%s\n", BOOL_STR(s->oper_edge_port)); break; case PARAM_TOPCHNGACK: printf("%s\n", BOOL_STR(s->tc_ack)); break; case PARAM_P2P: printf("%s\n", BOOL_STR(s->oper_p2p)); break; case PARAM_ADMINP2P: printf("%s\n", ADMIN_P2P_STR(s->admin_p2p)); break; case PARAM_RESTRROLE: printf("%s\n", BOOL_STR(s->restricted_role)); break; case PARAM_RESTRTCN: printf("%s\n", BOOL_STR(s->restricted_tcn)); break; case PARAM_PORTHELLOTIME: printf("%hhu\n", s->port_hello_time); break; case PARAM_DISPUTED: printf("%s\n", BOOL_STR(s->disputed)); break; case PARAM_BPDUGUARDPORT: printf("%s\n", BOOL_STR(s->bpdu_guard_port)); break; case PARAM_BPDUGUARDERROR: printf("%s\n", BOOL_STR(s->bpdu_guard_error)); break; case PARAM_BPDUFILTERPORT: printf("%s\n", BOOL_STR(s->bpdu_filter_port)); break; case PARAM_NETWORKPORT: printf("%s\n", BOOL_STR(s->network_port)); break; case PARAM_BA_INCONSISTENT: printf("%s\n", BOOL_STR(s->ba_inconsistent)); break; case PARAM_NUMTXBPDU: printf("%u\n", s->num_tx_bpdu); break; case PARAM_NUMRXBPDU: printf("%u\n", s->num_rx_bpdu); break; case PARAM_NUMTXTCN: printf("%u\n", s->num_tx_tcn); break; case PARAM_NUMRXTCN: printf("%u\n", s->num_rx_tcn); break; case PARAM_NUMTRANSFWD: printf("%u\n", s->num_trans_fwd); break; case PARAM_NUMTRANSBLK: printf("%u\n", s->num_trans_blk); break; case PARAM_NUMBPDUFILTERED: printf("%u\n", s->num_rx_bpdu_filtered); break; case PARAM_RCVDBPDU: printf("%s\n", BOOL_STR(s->rcvdBpdu)); break; case PARAM_RCVDSTP: printf("%s\n", BOOL_STR(s->rcvdSTP)); break; case PARAM_RCVDRSTP: printf("%s\n", BOOL_STR(s->rcvdRSTP)); break; case PARAM_SENDRSTP: printf("%s\n", BOOL_STR(s->sendRSTP)); break; case PARAM_RCVDTCACK: printf("%s\n", BOOL_STR(s->rcvdTcAck)); break; case PARAM_RCVDTCN: printf("%s\n", BOOL_STR(s->rcvdTcn)); break; default: return -2; /* -2 = unknown param */ } return 0; } static int do_showport_fmt_json(const CIST_PortStatus *s, const char *bridge_name, const char *port_name, param_id_t param_id) { switch(param_id) { case PARAM_NULL: if(detail) { printf("{"); printf("\"port\":\"%s\",", port_name); printf("\"bridge\":\"%s\",", bridge_name); printf("\"enabled\":\"%s\",", BOOL_STR(s->enabled)); printf("\"role\":\"%s\",", ROLE_STR(s->role)); printf("\"port-id\":\""PRT_ID_FMT"\",", PRT_ID_ARGS(s->port_id)); printf("\"state\":\"%s\",", STATE_STR(s->state)); printf("\"external-port-cost\":\"%u\",", s->external_port_path_cost); printf("\"internal-port-cost\":\"%u\",", s->internal_port_path_cost); printf("\"admin-external-cost\":\"%u\",", s->admin_external_port_path_cost); printf("\"admin-internal-cost\":\"%u\",", s->admin_internal_port_path_cost); printf("\"designated-root\":\""BR_ID_FMT"\",", BR_ID_ARGS(s->designated_root)); printf("\"dsgn-external-cost\":\"%u\",", s->designated_external_cost); printf("\"dsgn-regional-root\":\""BR_ID_FMT"\",", BR_ID_ARGS(s->designated_regional_root)); printf("\"dsgn-internal-cost\":\"%u\",", s->designated_internal_cost); printf("\"designated-bridge\":\""BR_ID_FMT"\",", BR_ID_ARGS(s->designated_bridge)); printf("\"designated-port\":\""PRT_ID_FMT"\",", PRT_ID_ARGS(s->designated_port)); printf("\"admin-edge-port\":\"%s\",", BOOL_STR(s->admin_edge_port)); printf("\"auto-edge-port\":\"%s\",", BOOL_STR(s->auto_edge_port)); printf("\"oper-edge-port\":\"%s\",", BOOL_STR(s->oper_edge_port)); printf("\"topology-change-ack\":\"%s\",", BOOL_STR(s->tc_ack)); printf("\"point-to-point\":\"%s\",", BOOL_STR(s->oper_p2p)); printf("\"admin-point-to-point\":\"%s\",", ADMIN_P2P_STR(s->admin_p2p)); printf("\"restricted-role\":\"%s\",", BOOL_STR(s->restricted_role)); printf("\"restricted-TCN\":\"%s\",", BOOL_STR(s->restricted_tcn)); printf("\"port-hello-time\":\"%hhu\",", s->port_hello_time); printf("\"disputed\":\"%s\",", BOOL_STR(s->disputed)); printf("\"bpdu-guard-port\":\"%s\",", BOOL_STR(s->bpdu_guard_port)); printf("\"bpdu-guard-error\":\"%s\",", BOOL_STR(s->bpdu_guard_error)); printf("\"bpdu-filter-port\":\"%s\",", BOOL_STR(s->bpdu_filter_port)); printf("\"network-port\":\"%s\",", BOOL_STR(s->network_port)); printf("\"ba-inconsistent\":\"%s\",", BOOL_STR(s->ba_inconsistent)); printf("\"num-tx-bpdu\":\"%u\",", s->num_tx_bpdu); printf("\"num-rx-bpdu\":\"%u\",", s->num_rx_bpdu); printf("\"num-rx-bpdu-filtered\":\"%u\",", s->num_rx_bpdu_filtered); printf("\"num-tx-tcn\":\"%u\",", s->num_tx_tcn); printf("\"num-rx-tcn\":\"%u\",", s->num_rx_tcn); printf("\"num-transition-fwd\":\"%u\",", s->num_trans_fwd); printf("\"num-transition-blk\":\"%u\",", s->num_trans_blk); printf("\"received-bpdu\":\"%s\",", BOOL_STR(s->rcvdBpdu)); printf("\"received-stp\":\"%s\",", BOOL_STR(s->rcvdSTP)); printf("\"received-rstp\":\"%s\",", BOOL_STR(s->rcvdRSTP)); printf("\"received-tc-ack\":\"%s\",", BOOL_STR(s->rcvdTcAck)); printf("\"received-tcn\":\"%s\",", BOOL_STR(s->rcvdTcn)); printf("\"send-rstp\":\"%s\"", BOOL_STR(s->sendRSTP)); printf("}"); } else { printf("{" "\"port\":\"%s\"," "\"bridge\":\"%s\"," "\"point-to-point\":\"%s\"," "\"oper-edge-port\":\"%s\"," "\"port-id\":\""PRT_ID_FMT"\"," "\"enabled\":\"%s\"," "\"state\":\"%s\"," "\"role\":\"%s\"," "\"designated-bridge\":\""BR_ID_FMT"\"," "\"designated-port\":\""PRT_ID_FMT"\"," "\"designated-root\":\""BR_ID_FMT"\"" "}", port_name, bridge_name, BOOL_STR(s->oper_p2p), BOOL_STR(s->oper_edge_port), PRT_ID_ARGS(s->port_id), BOOL_STR(s->enabled), STATE_STR(s->state), ROLE_STR(s->role), BR_ID_ARGS(s->designated_bridge), PRT_ID_ARGS(s->designated_port), BR_ID_ARGS(s->designated_root)); } break; case PARAM_ENABLED: case PARAM_ROLE: case PARAM_STATE: case PARAM_PORTID: case PARAM_EXTPORTCOST: case PARAM_ADMINEXTCOST: case PARAM_INTPORTCOST: case PARAM_ADMININTCOST: case PARAM_DSGNROOT: case PARAM_DSGNEXTCOST: case PARAM_DSGNRROOT: case PARAM_DSGNINTCOST: case PARAM_DSGNBR: case PARAM_DSGNPORT: case PARAM_ADMINEDGEPORT: case PARAM_AUTOEDGEPORT: case PARAM_OPEREDGEPORT: case PARAM_TOPCHNGACK: case PARAM_P2P: case PARAM_ADMINP2P: case PARAM_RESTRROLE: case PARAM_RESTRTCN: case PARAM_PORTHELLOTIME: case PARAM_DISPUTED: case PARAM_BPDUGUARDPORT: case PARAM_BPDUGUARDERROR: case PARAM_NETWORKPORT: case PARAM_BA_INCONSISTENT: case PARAM_NUMTXBPDU: case PARAM_NUMRXBPDU: case PARAM_NUMTXTCN: case PARAM_NUMRXTCN: case PARAM_NUMTRANSFWD: case PARAM_NUMTRANSBLK: case PARAM_NUMBPDUFILTERED: case PARAM_RCVDBPDU: case PARAM_RCVDSTP: case PARAM_RCVDRSTP: case PARAM_SENDRSTP: case PARAM_RCVDTCACK: case PARAM_RCVDTCN: /* Output individual parameters for the JSON format as plain text in quotes */ printf("\""); int err = do_showport_fmt_plain(s, bridge_name, port_name, param_id); printf("\""); return err; default: return -2; /* -2 = unknown param */ } return 0; } static int do_showport(int br_index, const char *bridge_name, const char *port_name, param_id_t param_id) { CIST_PortStatus s; int r = 0; int port_index = get_index_die(port_name, "port", false); if(0 > port_index) return port_index; if((r = CTL_get_cist_port_status(br_index, port_index, &s))) { fprintf(stderr, "%s:%s Failed to get port state\n", bridge_name, port_name); return -1; } switch(format) { case FORMAT_PLAIN: return do_showport_fmt_plain(&s, bridge_name, port_name, param_id); case FORMAT_JSON: return do_showport_fmt_json(&s, bridge_name, port_name, param_id); default: return -3; /* -3 = unsupported or unknown format */ } } static int not_dot_dotdot(const struct dirent *entry) { const char *n = entry->d_name; return !('.' == n[0] && (0 == n[1] || ('.' == n[1] && 0 == n[2]))); } static int get_port_list(const char *br_ifname, struct dirent ***namelist) { int res; char buf[SYSFS_PATH_MAX]; /* strlen(sysfs_class_net) + strlen("/%.230s/brif") must be < sizeof(buf) to prevent truncation ; gcc7's fortify headers complain about that */ snprintf(buf, sizeof(buf), SYSFS_CLASS_NET "/%.230s/brif", br_ifname); if(0 > (res = scandir(buf, namelist, not_dot_dotdot, versionsort))) fprintf(stderr, "Error getting list of all ports of bridge %s\n", br_ifname); return res; } static int cmd_showport(int argc, char *const *argv) { int r = 0; int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int i, count = 0; struct dirent **namelist; param_id_t param_id = PARAM_NULL; if(2 < argc) { count = argc - 2; if(1 < count) { /* check if last argument is known parameter name */ for(i = 0; i < COUNT_OF(cist_port_params); ++i) { if(0 == strcmp(argv[argc - 1], cist_port_params[i].str)) { param_id = cist_port_params[i].id; --count; break; } } } } else { if(0 > (count = get_port_list(argv[1], &namelist))) return count; } do_arraystart_fmt(); for(i = 0; i < count; ++i) { const char *name; if(2 < argc) name = argv[i + 2]; else name = namelist[i]->d_name; if(i) do_arraynext_fmt(); int err = do_showport(br_index, argv[1], name, param_id); if(err) r = err; } do_arrayend_fmt(); if(2 >= argc) { for(i = 0; i < count; ++i) free(namelist[i]); free(namelist); } return r; } static int cmd_showportparams(int argc, char *const *argv) { int r = 0; int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int i, count = argc - 3; param_id_t param_id[count]; for(i = 0; i < count; ++i) { int err = 1; for(int j = 0; j < COUNT_OF(cist_port_params); ++j) { if(0 == strcmp(argv[argc - count + i], cist_port_params[j].str)) { param_id[i] = cist_port_params[j].id; err = 0; break; } } if (err) { fprintf(stderr, "Can't find index for %s param. " "Not a valid CSTP param.\n", argv[argc - count + i]); return -1; } } do_arraystart_fmt(); for(i = 0; i < count; ++i) { const char *intf_name; intf_name = argv[2]; if(i) do_arraynext_fmt(); int err = do_showport(br_index, argv[1], intf_name, param_id[i]); if(err) r = err; } do_arrayend_fmt(); return r; } static int cmd_showportdetail(int argc, char *const *argv) { detail = 1; return cmd_showport(argc, argv); } static int do_showtreeport_fmt_plain(const MSTI_PortStatus *s, const char *br_name, const char *port_name, int mstid) { printf("%s:%s MSTI %hu info\n", br_name, port_name, (unsigned short)mstid); printf(" role %-23s ", ROLE_STR(s->role)); printf("port id "PRT_ID_FMT"\n", PRT_ID_ARGS(s->port_id)); printf(" state %-23s ", STATE_STR(s->state)); printf("disputed %s\n", BOOL_STR(s->disputed)); printf(" internal port cost %-23u ", s->internal_port_path_cost); printf("admin internal cost %u\n", s->admin_internal_port_path_cost); printf(" dsgn regional root "BR_ID_FMT" ", BR_ID_ARGS(s->designated_regional_root)); printf("dsgn internal cost %u\n", s->designated_internal_cost); printf(" designated bridge "BR_ID_FMT" ", BR_ID_ARGS(s->designated_bridge)); printf("designated port "PRT_ID_FMT"\n", PRT_ID_ARGS(s->designated_port)); return 0; } static int do_showtreeport_fmt_json(const MSTI_PortStatus *s, const char *br_name, const char *port_name, int mstid) { printf("{"); printf("\"port\":\"%s\",", port_name); printf("\"bridge\":\"%s\",", br_name); printf("\"mstid\":\"%hu\",", (unsigned short)mstid); printf("\"role\":\"%s\",", ROLE_STR(s->role)); printf("\"port-id\":\""PRT_ID_FMT"\",", PRT_ID_ARGS(s->port_id)); printf("\"state\":\"%s\",", STATE_STR(s->state)); printf("\"disputed\":\"%s\",", BOOL_STR(s->disputed)); printf("\"internal-port-cost\":\"%u\",", s->internal_port_path_cost); printf("\"admin-internal-cost\":\"%u\",", s->admin_internal_port_path_cost); printf("\"dsgn-regional-root\":\""BR_ID_FMT"\",", BR_ID_ARGS(s->designated_regional_root)); printf("\"dsgn-internal-cost\":\"%u\",", s->designated_internal_cost); printf("\"designated-bridge\":\""BR_ID_FMT"\",", BR_ID_ARGS(s->designated_bridge)); printf("\"designated-port\":\""PRT_ID_FMT"\"", PRT_ID_ARGS(s->designated_port)); printf("}"); return 0; } static int cmd_showtreeport(int argc, char *const *argv) { MSTI_PortStatus s; int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int port_index = get_index(argv[2], "port"); if(0 > port_index) return port_index; int mstid = get_id(argv[3], "mstid", MAX_MSTID); if(0 > mstid) return mstid; if(CTL_get_msti_port_status(br_index, port_index, mstid, &s)) return -1; switch(format) { case FORMAT_PLAIN: return do_showtreeport_fmt_plain(&s, argv[1], argv[2], mstid); case FORMAT_JSON: return do_showtreeport_fmt_json(&s, argv[1], argv[2], mstid); default: return -3; /* -3 = unsupported or unknown format */ } return 0; } static int cmd_addbridge(int argc, char *const *argv) { int i, j, res, ifcount, brcount = argc - 1; int *br_array; int* *ifaces_lists; if(NULL == (br_array = malloc((brcount + 1) * sizeof(int)))) { out_of_memory_exit: fprintf(stderr, "out of memory, brcount = %d\n", brcount); return -1; } if(NULL == (ifaces_lists = malloc(brcount * sizeof(int*)))) { free(br_array); goto out_of_memory_exit; } br_array[0] = brcount; for(i = 1; i <= brcount; ++i) { struct dirent **namelist; br_array[i] = get_index(argv[i], "bridge"); if(0 > (ifcount = get_port_list(argv[i], &namelist))) { ifaces_error_exit: for(i -= 2; i >= 0; --i) free(ifaces_lists[i]); free(ifaces_lists); free(br_array); return ifcount; } if(NULL == (ifaces_lists[i - 1] = malloc((ifcount + 1) * sizeof(int)))) { fprintf(stderr, "out of memory, bridge %s, ifcount = %d\n", argv[i], ifcount); for(j = 0; j < ifcount; ++j) free(namelist[j]); free(namelist); ifcount = -1; goto ifaces_error_exit; } ifaces_lists[i - 1][0] = ifcount; for(j = 1; j <= ifcount; ++j) { ifaces_lists[i - 1][j] = get_index(namelist[j - 1]->d_name, "port"); free(namelist[j - 1]); } free(namelist); } res = CTL_add_bridges(br_array, ifaces_lists); for(i = 0; i < brcount; ++i) free(ifaces_lists[i]); free(ifaces_lists); free(br_array); return res; } static int cmd_delbridge(int argc, char *const *argv) { int i, res, brcount = argc - 1; int *br_array; if(NULL == (br_array = malloc((brcount + 1) * sizeof(int)))) { fprintf(stderr, "out of memory, brcount = %d\n", brcount); return -1; } br_array[0] = brcount; for(i = 1; i <= brcount; ++i) br_array[i] = get_index(argv[i], "bridge"); res = CTL_del_bridges(br_array); free(br_array); return res; } static unsigned int getuint(const char *s) { char *end; long l; l = strtoul(s, &end, 0); if(0 == *s || 0 != *end || INT_MAX < l) { fprintf(stderr, "Invalid unsigned int arg %s\n", s); exit(1); } return l; } static int getenum(const char *s, const char *opt[]) { int i; for(i = 0; opt[i] != NULL; ++i) if(0 == strcmp(s, opt[i])) return i; fprintf(stderr, "Invalid argument %s: expecting one of ", s); for(i = 0; opt[i] != NULL; ++i) fprintf(stderr, "%s%s", opt[i], (opt[i + 1] ? ", " : "\n")); exit(1); } static int getyesno(const char *s, const char *yes, const char *no) { /* Reverse yes and no so error message looks more normal */ const char *opt[] = { yes, no, NULL }; return 1 - getenum(s, opt); } static int cmd_setmstconfid(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; unsigned int revision = getuint(argv[2]); if(revision > 0xFFFF) { fprintf(stderr, "Bad revision %s\n", argv[2]); return -1; } return CTL_set_mstconfid(br_index, revision, (__u8 *)argv[3]); } #define set_bridge_cfg(field, value) \ ({ \ CIST_BridgeConfig c; \ memset(&c, 0, sizeof(c)); \ c.field = value; \ c.set_ ## field = true; \ int r = CTL_set_cist_bridge_config(br_index, &c); \ if(r) \ printf("Couldn't change bridge " #field "\n"); \ r; \ }) #define set_port_cfg(field, value) \ ({ \ CIST_PortConfig c; \ memset(&c, 0, sizeof(c)); \ c.field = value; \ c.set_ ## field = true; \ int r = CTL_set_cist_port_config(br_index, port_index, &c); \ if(r) \ printf("Couldn't change port " #field "\n"); \ r; \ }) #define set_tree_port_cfg(field, value) \ ({ \ MSTI_PortConfig c; \ memset(&c, 0, sizeof(c)); \ c.field = value; \ c.set_ ## field = true; \ int r = CTL_set_msti_port_config(br_index, port_index, mstid, &c); \ if(r) \ printf("Couldn't change per-tree port " #field "\n"); \ r; \ }) static int cmd_setbridgemaxage(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; unsigned int max_age = getuint(argv[2]); if(max_age > 255) max_age = 255; return set_bridge_cfg(bridge_max_age, max_age); } static int cmd_setbridgehello(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; unsigned int hello_time = getuint(argv[2]); if(hello_time > 255) hello_time = 255; return set_bridge_cfg(bridge_hello_time, hello_time); } static int cmd_setbridgefdelay(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; unsigned int forward_delay = getuint(argv[2]); if(forward_delay > 255) forward_delay = 255; return set_bridge_cfg(bridge_forward_delay, forward_delay); } static int cmd_setbridgemaxhops(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; unsigned int max_hops = getuint(argv[2]); if(max_hops > 255) max_hops = 255; return set_bridge_cfg(max_hops, max_hops); } static int cmd_setbridgeforcevers(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; const char *opts[] = { "stp", "rstp", "mstp", NULL }; int vals[] = { protoSTP, protoRSTP, protoMSTP }; return set_bridge_cfg(protocol_version, vals[getenum(argv[2], opts)]); } static int cmd_setbridgetxholdcount(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; return set_bridge_cfg(tx_hold_count, getuint(argv[2])); } static int cmd_setbridgeageing(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; return set_bridge_cfg(bridge_ageing_time, getuint(argv[2])); } static int cmd_settreeprio(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int mstid = get_id(argv[2], "mstid", MAX_MSTID); if(0 > mstid) return mstid; unsigned int prio = getuint(argv[3]); if(prio > 255) prio = 255; return CTL_set_msti_bridge_config(br_index, mstid, prio); } static int cmd_setportpathcost(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int port_index = get_index(argv[2], "port"); if(0 > port_index) return port_index; return set_port_cfg(admin_external_port_path_cost, getuint(argv[3])); } static int cmd_setportadminedge(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int port_index = get_index(argv[2], "port"); if(0 > port_index) return port_index; return set_port_cfg(admin_edge_port, getyesno(argv[3], "yes", "no")); } static int cmd_setportautoedge(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int port_index = get_index(argv[2], "port"); if(0 > port_index) return port_index; return set_port_cfg(auto_edge_port, getyesno(argv[3], "yes", "no")); } static int cmd_setportp2p(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int port_index = get_index(argv[2], "port"); if(0 > port_index) return port_index; const char *opts[] = { "no", "yes", "auto", NULL }; int vals[] = { p2pForceFalse, p2pForceTrue, p2pAuto }; return set_port_cfg(admin_p2p, vals[getenum(argv[3], opts)]); } static int cmd_setportrestrrole(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int port_index = get_index(argv[2], "port"); if(0 > port_index) return port_index; return set_port_cfg(restricted_role, getyesno(argv[3], "yes", "no")); } static int cmd_setportrestrtcn(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int port_index = get_index(argv[2], "port"); if(0 > port_index) return port_index; return set_port_cfg(restricted_tcn, getyesno(argv[3], "yes", "no")); } static int cmd_setportbpduguard(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int port_index = get_index(argv[2], "port"); if(0 > port_index) return port_index; return set_port_cfg(bpdu_guard_port, getyesno(argv[3], "yes", "no")); } static int cmd_setportbpdufilter(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if (0 > br_index) return br_index; int port_index = get_index(argv[2], "port"); if (0 > port_index) return port_index; return set_port_cfg(bpdu_filter_port, getyesno(argv[3], "yes", "no")); } static int cmd_setportnetwork(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if (0 > br_index) return br_index; int port_index = get_index(argv[2], "port"); if (0 > port_index) return port_index; return set_port_cfg(network_port, getyesno(argv[3], "yes", "no")); } static int cmd_setportdonttxmt(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if (0 > br_index) return br_index; int port_index = get_index(argv[2], "port"); if (0 > port_index) return port_index; return set_port_cfg(dont_txmt, getyesno(argv[3], "yes", "no")); } static int cmd_settreeportprio(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int port_index = get_index(argv[2], "port"); if(0 > port_index) return port_index; int mstid = get_id(argv[3], "mstid", MAX_MSTID); if(0 > mstid) return mstid; unsigned int prio = getuint(argv[4]); if(prio > 255) prio = 255; return set_tree_port_cfg(port_priority, prio); } static int cmd_settreeportcost(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int port_index = get_index(argv[2], "port"); if(0 > port_index) return port_index; int mstid = get_id(argv[3], "mstid", MAX_MSTID); if(0 > mstid) return mstid; return set_tree_port_cfg(admin_internal_port_path_cost, getuint(argv[4])); } static int cmd_portmcheck(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int port_index = get_index(argv[2], "port"); if(0 > port_index) return port_index; return CTL_port_mcheck(br_index, port_index); } static int cmd_debuglevel(int argc, char *const *argv) { return CTL_set_debug_level(getuint(argv[1])); } static int do_showmstilist_fmt_plain(const char *br_name, int num_mstis, const __u16 *mstids) { int i; printf("%s list of known MSTIs:\n", br_name); for(i = 0; i < num_mstis; ++i) printf(" %hu", mstids[i]); printf("\n"); return 0; } static int do_showmstilist_fmt_json(const char *br_name, int num_mstis, const __u16 *mstids) { int i; printf("{"); printf("\"bridge\":\"%s\",", br_name); printf("\"mstids\":["); for(i = 0; i < num_mstis; ++i) { if(i) printf(","); printf("\"%hu\"", mstids[i]); } printf("]}"); return 0; } static int cmd_showmstilist(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int num_mstis = 0; __u16 mstids[MAX_IMPLEMENTATION_MSTIS + 1]; /* +1 - for the CIST */ if(CTL_get_mstilist(br_index, &num_mstis, mstids)) return -1; switch(format) { case FORMAT_PLAIN: return do_showmstilist_fmt_plain(argv[1], num_mstis, mstids); case FORMAT_JSON: return do_showmstilist_fmt_json(argv[1], num_mstis, mstids); default: return -3; /* -3 = unsupported or unknown format */ } } static int do_showmstconfid_fmt_plain( const mst_configuration_identifier_t *cfgid, const char *br_name) { int i; printf("%s MST Configuration Identifier:\n", br_name); printf(" Format Selector: %hhu\n", cfgid->s.selector); printf(" Configuration Name: %.*s\n", CONFIGURATION_NAME_LEN, cfgid->s.configuration_name); printf(" Revision Level: %hu\n", __be16_to_cpu(cfgid->s.revision_level)); printf(" Configuration Digest: "); for(i = 0; i < CONFIGURATION_DIGEST_LEN; ++i) printf("%02hhX", cfgid->s.configuration_digest[i]); printf("\n"); return 0; } static int do_showmstconfid_fmt_json( const mst_configuration_identifier_t *cfgid, const char *br_name) { int i; printf("{"); printf("\"bridge\":\"%s\",", br_name); printf("\"format-selector\":\"%hhu\",", cfgid->s.selector); printf("\"configuration-name\":\"%.*s\",", CONFIGURATION_NAME_LEN, cfgid->s.configuration_name); printf("\"revision-level\":\"%hu\",", __be16_to_cpu(cfgid->s.revision_level)); printf("\"configuration-digest\":\""); for(i = 0; i < CONFIGURATION_DIGEST_LEN; ++i) printf("%02hhX", cfgid->s.configuration_digest[i]); printf ("\"}"); return 0; } static int cmd_showmstconfid(int argc, char *const *argv) { mst_configuration_identifier_t cfgid; int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; if(CTL_get_mstconfid(br_index, &cfgid)) return -1; switch(format) { case FORMAT_PLAIN: return do_showmstconfid_fmt_plain(&cfgid, argv[1]); case FORMAT_JSON: return do_showmstconfid_fmt_json(&cfgid, argv[1]); default: return -3; /* -3 = unsupported or unknown format */ } } static int cmd_createtree(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int mstid = get_id(argv[2], "mstid", MAX_MSTID); if(0 > mstid) return mstid; return CTL_create_msti(br_index, mstid); } static int cmd_deletetree(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; int mstid = get_id(argv[2], "mstid", MAX_MSTID); if(0 > mstid) return mstid; return CTL_delete_msti(br_index, mstid); } static int do_showvid2fid_fmt(__u16 *vid2fid, const char *br_name) { if(FORMAT_PLAIN == format) printf("%s VID-to-FID allocation table:\n", br_name); else if(FORMAT_JSON == format) { printf("{\"bridge\":\"%s\",", br_name); printf("\"vid2fid\":["); } unsigned int i; int out_count = 0; unsigned int interval_count; char first_char; vid2fid[MAX_VID + 1] = 0xFFFF; /* helps to finalize last interval */ do{ unsigned int cur_fid = vid2fid[1]; for(i = 1; i <= MAX_VID; ++i) if(cur_fid > vid2fid[i]) cur_fid = vid2fid[i]; if(cur_fid > MAX_FID) break; if(FORMAT_PLAIN == format) printf(" FID %u:", cur_fid); else if(FORMAT_JSON == format) { if(0 < out_count) printf(","); printf("{\"fid\":\"%u\",\"vid\":[", cur_fid); } first_char = ' '; for(i = 1, interval_count = 0; i <= (MAX_VID + 1); ++i) { if(cur_fid != vid2fid[i]) { if(interval_count) { if(FORMAT_PLAIN == format) printf("%c%u", first_char, i - interval_count); else if(FORMAT_JSON == format) printf("%c\"%u", first_char, i - interval_count); first_char = ','; if(1 < interval_count) printf("-%u", i - 1); if(FORMAT_JSON == format) printf("\""); interval_count = 0; } continue; } vid2fid[i] = 0xFFFF; ++interval_count; } if(FORMAT_PLAIN == format) printf("\n"); else if(FORMAT_JSON == format) printf("]}"); ++out_count; }while(true); if(FORMAT_JSON == format) printf("}"); return 0; } static int cmd_showvid2fid(int argc, char *const *argv) { __u16 vid2fid[MAX_VID + 2]; int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; if(CTL_get_vids2fids(br_index, vid2fid)) return -1; switch(format) { case FORMAT_PLAIN: case FORMAT_JSON: return do_showvid2fid_fmt(vid2fid, argv[1]); default: return -3; /* -3 = unsupported or unknown format */ } } static int do_showfid2mstid_fmt(__u16 *fid2mstid, const char *br_name) { if(FORMAT_PLAIN == format) printf("%s FID-to-MSTID allocation table:\n", br_name); else if(FORMAT_JSON == format) { printf("{\"bridge\":\"%s\",", br_name); printf("\"fid2mstid\":["); } unsigned int i; unsigned int interval_count; int out_count = 0; char first_char; fid2mstid[MAX_FID + 1] = 0xFFFF; /* helps to finalize last interval */ do{ unsigned int cur_mstid = fid2mstid[0]; for(i = 1; i <= MAX_FID; ++i) if(cur_mstid > fid2mstid[i]) cur_mstid = fid2mstid[i]; if(cur_mstid > MAX_MSTID) break; if(FORMAT_PLAIN == format) printf(" MSTID %u:", cur_mstid); else if(FORMAT_JSON == format) { if(0 < out_count) printf(","); printf("{\"mstid\":\"%u\",\"fid\":[", cur_mstid); } first_char = ' '; for(i = 0, interval_count = 0; i <= (MAX_FID + 1); ++i) { if(cur_mstid != fid2mstid[i]) { if(interval_count) { if(FORMAT_PLAIN == format) printf("%c%u", first_char, i - interval_count); else if(FORMAT_JSON == format) printf("%c\"%u", first_char, i - interval_count); first_char = ','; if(1 < interval_count) printf("-%u", i - 1); if(FORMAT_JSON == format) printf("\""); interval_count = 0; } continue; } fid2mstid[i] = 0xFFFF; ++interval_count; } if(FORMAT_PLAIN == format) printf("\n"); else if(FORMAT_JSON == format) printf("]}"); ++out_count; }while(true); if(FORMAT_JSON == format) printf("}"); return 0; } static int cmd_showfid2mstid(int argc, char *const *argv) { __u16 fid2mstid[MAX_FID + 2]; int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; if(CTL_get_fids2mstids(br_index, fid2mstid)) return -1; switch(format) { case FORMAT_PLAIN: case FORMAT_JSON: return do_showfid2mstid_fmt(fid2mstid, argv[1]); default: return -3; /* -3 = unsupported or unknown format */ } } static int ParseList(const char *str_c, __u16 *array, __u16 max_index, const char *index_doc, __u16 max_value, const char *value_doc, bool no_zero_index) { char *next, *str, *list = strdup(str_c); if(NULL == list) { fprintf(stderr, "Out of memory'\n"); return -ENOMEM; } str = list; while(('\0' != *list) && (':' != *list)) ++list; if('\0' == *list) { bad_format: free(str); fprintf(stderr, "Bad format in argument: '%s'\n", str_c); return -1; } *list++ = '\0'; int value = get_id(str, value_doc, max_value); if(0 > value) { free(str); return value; } int i, nn, first_index, last_index; bool end = false; next = list; do{ list = next; while(('\0' != *next) && (',' != *next)) ++next; if('\0' == *next) end = true; else *next++ = '\0'; nn = 0; first_index = -1; if((1 > sscanf(list, "%d%n", &first_index, &nn)) || (first_index < 0)) { /* check for '*' */ while((' ' == *list) || ('\t' == *list)) ++list; if('*' != *list) goto bad_format; for(i = (no_zero_index ? 1 : 0); i <= max_index; ++i) if(array[i] > max_value) array[i] = value; continue; } if((first_index > max_index) || (no_zero_index && (0 == first_index))) { bad_index: free(str); fprintf(stderr, "Bad %s %d in argument: '%s'\n", index_doc, first_index, str_c); return -1; } list += nn; while(('\0' != *list) && (('0' > *list) || ('9' < *list))) ++list; if('\0' == *list) last_index = first_index; else { last_index = -1; if((1 > sscanf(list, "%d", &last_index)) || (last_index < 0)) goto bad_format; if((last_index > max_index) || (no_zero_index && (0 == last_index))) { first_index = last_index; /* for proper error string */ goto bad_index; } } if(first_index > last_index) { i = first_index; first_index = last_index; last_index = i; } for(i = first_index; i <= last_index; ++i) array[i] = value; }while(!end); free(str); return 0; } static int cmd_setvid2fid(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; __u16 vids2fids[MAX_VID + 1]; memset(vids2fids, 0xFF, sizeof(vids2fids)); int i, ret; for(i = 2; i < argc; ++i) if(0 > (ret = ParseList(argv[i], vids2fids, MAX_VID, "VID", MAX_FID, "FID", true))) return ret; return CTL_set_vids2fids(br_index, vids2fids); } static int cmd_setfid2mstid(int argc, char *const *argv) { int br_index = get_index(argv[1], "bridge"); if(0 > br_index) return br_index; __u16 fids2mstids[MAX_FID + 1]; memset(fids2mstids, 0xFF, sizeof(fids2mstids)); int i, ret; for(i = 2; i < argc; ++i) if(0 > (ret = ParseList(argv[i], fids2mstids, MAX_FID, "FID", MAX_MSTID, "mstid", false))) return ret; return CTL_set_fids2mstids(br_index, fids2mstids); } struct command { int nargs; int optargs; const char *name; int (*func) (int argc, char *const *argv); const char *format; const char *help; }; static const struct command commands[] = { /* Add/delete bridges */ {1, 32, "addbridge", cmd_addbridge, " [ ...]", "Add bridges to the mstpd's list"}, {1, 32, "delbridge", cmd_delbridge, " [ ...]", "Remove bridges from the mstpd's list"}, /* Show global bridge */ {0, 32, "showbridge", cmd_showbridge, "[ ... [param]]", "Show bridge state for the CIST"}, {1, 0, "showmstilist", cmd_showmstilist, "", "Show list of registered MSTIs"}, {1, 0, "showmstconfid", cmd_showmstconfid, "", "Show MST ConfigId"}, {1, 0, "showvid2fid", cmd_showvid2fid, "", "Show VID-to-FID allocation table"}, {1, 0, "showfid2mstid", cmd_showfid2mstid, "", "Show FID-to-MSTID allocation table"}, /* Show global port */ {1, 32, "showport", cmd_showport, " [...[port] [param]]", "Show port state for the CIST"}, {3, 32, "showportparams", cmd_showportparams, " ...[param]", "Show port state for the CIST"}, {1, 32, "showportdetail", cmd_showportdetail, " [ ... [param]]", "Show port detailed state for the CIST"}, /* Show tree bridge */ {2, 0, "showtree", cmd_showtree, " ", "Show bridge state for the given MSTI"}, /* Show tree port */ {3, 0, "showtreeport", cmd_showtreeport, " ", "Show port detailed state for the given MSTI"}, /* Set global bridge */ {3, 0, "setmstconfid", cmd_setmstconfid, " ", "Set MST ConfigId elements: Revision Level (0-65535) and Name"}, {2, 32, "setvid2fid", cmd_setvid2fid, " : [: ...]", "Set VIDs-to-FIDs allocation"}, {2, 32, "setfid2mstid", cmd_setfid2mstid, " : [: ...]", "Set FIDs-to-MSTIDs allocation"}, {2, 0, "setmaxage", cmd_setbridgemaxage, " ", "Set bridge max age (6-40)"}, {2, 0, "setfdelay", cmd_setbridgefdelay, " ", "Set bridge forward delay (4-30)"}, {2, 0, "setmaxhops", cmd_setbridgemaxhops, " ", "Set bridge max hops (6-40)"}, {2, 0, "sethello", cmd_setbridgehello, " ", "Set bridge hello time (1-10)"}, {2, 0, "setageing", cmd_setbridgeageing, " ", "Set bridge ageing time (10-1000000)"}, {2, 0, "setforcevers", cmd_setbridgeforcevers, " {mstp|rstp|stp}", "Force Spanning Tree protocol version"}, {2, 0, "settxholdcount", cmd_setbridgetxholdcount, " ", "Set bridge transmit hold count (1-10)"}, /* Set tree bridge */ {2, 0, "createtree", cmd_createtree, " ", "Create new MSTI"}, {2, 0, "deletetree", cmd_deletetree, " ", "Delete existing MSTI"}, {3, 0, "settreeprio", cmd_settreeprio, " ", "Set bridge priority (0-15) for the given MSTI"}, /* Set global port */ {3, 0, "setportpathcost", cmd_setportpathcost, " ", "Set port external path cost for the CIST (0 = auto)"}, {3, 0, "setportadminedge", cmd_setportadminedge, " {yes|no}", "Set initial edge state"}, {3, 0, "setportautoedge", cmd_setportautoedge, " {yes|no}", "Enable auto transition to/from edge state"}, {3, 0, "setportp2p", cmd_setportp2p, " {yes|no|auto}", "Set p2p detection mode"}, {3, 0, "setportrestrrole", cmd_setportrestrrole, " {yes|no}", "Restrict port ability to take Root role"}, {3, 0, "setportrestrtcn", cmd_setportrestrtcn, " {yes|no}", "Restrict port ability to propagate received TCNs"}, {2, 0, "portmcheck", cmd_portmcheck, " ", "Try to get back from STP to rapid (RSTP/MSTP) mode"}, {3, 0, "setbpduguard", cmd_setportbpduguard, " {yes|no}", "Set bpdu guard state"}, /* Set tree port */ {4, 0, "settreeportprio", cmd_settreeportprio, " ", "Set port priority (0-15) for the given MSTI"}, {4, 0, "settreeportcost", cmd_settreeportcost, " ", "Set port internal path cost for the given MSTI (0 = auto)"}, {3, 0, "setportnetwork", cmd_setportnetwork, " {yes|no}", "Set port network state"}, {3, 0, "setportdonttxmt", cmd_setportdonttxmt, " {yes|no}", "Disable/Enable sending BPDU"}, {3, 0, "setportbpdufilter", cmd_setportbpdufilter, " {yes|no}", "Set BPDU filter state"}, /* Other */ {1, 0, "debuglevel", cmd_debuglevel, "", "Level of verbosity"}, }; static const struct command *command_lookup(const char *cmd) { int i; for(i = 0; i < COUNT_OF(commands); ++i) { if(!strcmp(cmd, commands[i].name)) return &commands[i]; } return NULL; } static void command_helpall(void) { int i; for(i = 0; i < COUNT_OF(commands); ++i) { if(strcmp("setportdonttxmt", commands[i].name)) printf("-%s:\n %-16s %s\n", commands[i].help, commands[i].name, commands[i].format); } } static void help(void) { printf("Usage: mstpctl [options] [commands]\n"); printf("options:\n"); printf(" -h | --help Show this help text\n"); printf(" -V | --version Show version\n"); printf(" -b | --batch Process file with mstpctl commands\n"); printf(" -s | --stdin Process mstpctl commands from stdin\n"); printf(" Make sure to provide newlines between\n"); printf(" commands. Won't work if `batch` is used\n"); printf(" -i | --ignore Ignore failing commands during batch\n"); printf(" processing\n"); printf(" -f | --format Select output format (json, plain)\n"); printf("commands:\n"); command_helpall(); } static const struct command *command_lookup_and_validate(int argc, char *const *argv, int line_num) { const struct command *cmd; cmd = command_lookup(argv[0]); if(!cmd) { if (line_num > 0) fprintf(stderr, "Error on line %d:\n", line_num); fprintf(stderr, "Unknown command [%s]\n", argv[0]); if (line_num == 0) help(); return NULL; } if(argc < cmd->nargs + 1 || argc > cmd->nargs + cmd->optargs + 1) { if (line_num > 0) fprintf(stderr, "Error on line %d:\n", line_num); fprintf(stderr, "Incorrect number of arguments for command '%s'\n", cmd->name); fprintf(stderr, "Usage: mstpctl %s %s\n %s\n", cmd->name, cmd->format, cmd->help); return NULL; } return cmd; } static int split_line_into_parts(char *line, char **argv, int argv_size) { const char *delim = " \n"; char *ptr = strtok(line, delim); int cnt = 0; while (ptr) { argv[cnt] = ptr; ptr = strtok(NULL, delim); cnt++; if (cnt >= argv_size) return -1; } return cnt; } bool skip_line(const char *line) { /* empty line or comment; comment is marked as # at beginning of line */ if (line[0] == '\0' || line[0] == '\n' || line[0] == '#') return true; return false; } static int __process_batch_cmds(FILE *batch_file, bool run, bool ignore) { const struct command *cmd; char line[64], *argv[8]; int line_num, argc, cmds, rc; cmds = 0; line_num = 0; while (fgets(line, sizeof(line), batch_file)) { line_num++; if (skip_line(line)) continue; argc = split_line_into_parts(line, argv, 8); if (argc < 0) { fprintf(stderr, "Too many elements on line '%d'\n", line_num); return -1; } /* ignore lines with whitespace */ if (argc == 0) continue; cmd = command_lookup_and_validate(argc, argv, line_num); if (!cmd) { if (ignore) continue; return -1; } if (run) { rc = cmd->func(argc, argv); if (rc) { if (ignore) continue; return -1; } } cmds++; } return cmds; } static int process_batch_cmds(FILE *batch_file, bool ignore, bool is_stdin) { int rc; if (is_stdin) goto skip_batch_validation; /* Do some basic argv + argc validation for all commands first */ rc = __process_batch_cmds(batch_file, false, ignore); if (rc < 0) return 1; /* nothing do, exit with no error */ if (rc == 0) return 0; /* go at beginning of file and start over*/ fseek(batch_file, 0, SEEK_SET); skip_batch_validation: rc = __process_batch_cmds(batch_file, true, ignore); if (rc < 0) return 1; return 0; } int main(int argc, char *const *argv) { const struct command *cmd; int f, rc; static const struct option options[] = { {.name = "help", .val = 'h'}, {.name = "version", .val = 'V'}, {.name = "batch", .val = 'b', .has_arg = 1}, {.name = "stdin", .val = 's'}, {.name = "ignore", .val = 'i'}, {.name = "format", .val = 'f', .has_arg = 1}, {0} }; FILE *batch_file = NULL; bool is_stdin = false; bool ignore = false; while(EOF != (f = getopt_long(argc, argv, "Vhf:b:is", options, NULL))) switch(f) { case 'h': help(); return 0; case 'V': printf(PACKAGE_VERSION "\n"); return 0; case 'b': if (is_stdin) { fprintf(stderr, "Cannot mix stdin & batch file\n"); goto help; } if (!optarg || !strlen(optarg)) { fprintf(stderr, "No batch file provided\n"); goto help; } batch_file = fopen(optarg, "rb"); if (!batch_file) { fprintf(stderr, "Could not open file '%s'\n", optarg); goto help; } break; case 's': if (batch_file) { fprintf(stderr, "Cannot mix stdin & batch file\n"); goto help; } batch_file = stdin; is_stdin = true; break; case 'i': ignore = true; break; case 'f': if (!strcmp(optarg, "json")) format = FORMAT_JSON; else if (!strcmp(optarg, "plain")) format = FORMAT_PLAIN; else { fprintf(stderr, "Invalid format '%s'\n", optarg); goto help; } break; default: fprintf(stderr, "Unknown option '%c'\n", f); goto help; } if((argc == optind) && !batch_file) goto help; if(ctl_client_init()) { fprintf(stderr, "can't setup control connection\n"); return 1; } if (batch_file) { rc = process_batch_cmds(batch_file, ignore, is_stdin); if (!is_stdin) fclose(batch_file); return rc; } argc -= optind; argv += optind; cmd = command_lookup_and_validate(argc, argv, 0); if(!cmd) return 1; return cmd->func(argc, argv); help: help(); return 1; } /* Implementation of client-side functions */ CLIENT_SIDE_FUNCTION(get_cist_bridge_status) CLIENT_SIDE_FUNCTION(get_msti_bridge_status) CLIENT_SIDE_FUNCTION(set_cist_bridge_config) CLIENT_SIDE_FUNCTION(set_msti_bridge_config) CLIENT_SIDE_FUNCTION(get_cist_port_status) CLIENT_SIDE_FUNCTION(get_msti_port_status) CLIENT_SIDE_FUNCTION(set_cist_port_config) CLIENT_SIDE_FUNCTION(set_msti_port_config) CLIENT_SIDE_FUNCTION(port_mcheck) CLIENT_SIDE_FUNCTION(set_debug_level) CLIENT_SIDE_FUNCTION(get_mstilist) CLIENT_SIDE_FUNCTION(create_msti) CLIENT_SIDE_FUNCTION(delete_msti) CLIENT_SIDE_FUNCTION(get_mstconfid) CLIENT_SIDE_FUNCTION(set_mstconfid) CLIENT_SIDE_FUNCTION(get_vids2fids) CLIENT_SIDE_FUNCTION(get_fids2mstids) CLIENT_SIDE_FUNCTION(set_vid2fid) CLIENT_SIDE_FUNCTION(set_fid2mstid) CLIENT_SIDE_FUNCTION(set_vids2fids) CLIENT_SIDE_FUNCTION(set_fids2mstids) CTL_DECLARE(add_bridges) { int res = 0; LogString log = { .buf = "" }; int i, chunk_count, brcount, serialized_data_count; int *serialized_data, *ptr; chunk_count = serialized_data_count = (brcount = br_array[0]) + 1; for(i = 0; i < brcount; ++i) serialized_data_count += ifaces_lists[i][0] + 1; if(NULL == (serialized_data = malloc(serialized_data_count * sizeof(int)))) { LOG("out of memory, serialized_data_count = %d", serialized_data_count); return -1; } memcpy(serialized_data, br_array, chunk_count * sizeof(int)); ptr = serialized_data + chunk_count; for(i = 0; i < brcount; ++i) { chunk_count = ifaces_lists[i][0] + 1; memcpy(ptr, ifaces_lists[i], chunk_count * sizeof(int)); ptr += chunk_count; } int r = send_ctl_message(CMD_CODE_add_bridges, serialized_data, serialized_data_count * sizeof(int), NULL, 0, &log, &res); free(serialized_data); if(r || res) LOG("Got return code %d, %d\n%s", r, res, log.buf); if(r) return r; if(res) return res; return 0; } CTL_DECLARE(del_bridges) { int res = 0; LogString log = { .buf = "" }; int r = send_ctl_message(CMD_CODE_del_bridges, br_array, (br_array[0] + 1) * sizeof(int), NULL, 0, &log, &res); if(r || res) LOG("Got return code %d, %d\n%s", r, res, log.buf); if(r) return r; if(res) return res; return 0; } /*********************** Logging *********************/ void Dprintf(int level, const char *fmt, ...) { char logbuf[LOG_STRING_LEN]; logbuf[sizeof(logbuf) - 1] = 0; va_list ap; va_start(ap, fmt); vsnprintf(logbuf, sizeof(logbuf) - 1, fmt, ap); va_end(ap); printf("%s\n", logbuf); } mstpd-mstpd-8995e5d/ctl_socket_client.c000066400000000000000000000107331516242366700202200ustar00rootroot00000000000000/***************************************************************************** Copyright (c) 2006 EMC Corporation. Copyright (c) 2011 Factor-SPE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. Authors: Srinivas Aji Authors: Vitalii Demianets ******************************************************************************/ #include #include #include #include "ctl_functions.h" #define NO_DAEMON #include "log.h" static int fd = -1; int ctl_client_init(void) { struct sockaddr_un sa_svr; int s; TST(strlen(MSTP_SERVER_SOCK_NAME) < sizeof(sa_svr.sun_path), -1); if(0 > (s = socket(PF_UNIX, SOCK_DGRAM, 0))) { ERROR("Couldn't open unix socket: %m"); return -1; } set_socket_address(&sa_svr, MSTP_SERVER_SOCK_NAME); struct sockaddr_un sa; char tmpname[64]; sprintf(tmpname, "MSTPCTL_%d", getpid()); set_socket_address(&sa, tmpname); /* We need this bind. The autobind on connect isn't working properly. * The server doesn't get a proper sockaddr in recvmsg if we don't do this. */ if(0 != bind(s, (struct sockaddr *)&sa, sizeof(sa))) { ERROR("Couldn't bind socket: %m"); close(s); return -1; } if(0 != connect(s, (struct sockaddr *)&sa_svr, sizeof(sa_svr))) { ERROR("Couldn't connect to server"); close(s); return -1; } fd = s; return 0; } void ctl_client_cleanup(void) { if(fd >= 0) { close(fd); fd = -1; } } int send_ctl_message(int cmd, void *inbuf, int lin, void *outbuf, int lout, LogString *log, int *res) { struct ctl_msg_hdr mhdr; struct msghdr msg; struct iovec iov[3]; int l; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 3; msg.msg_control = NULL; msg.msg_controllen = 0; mhdr.cmd = cmd; mhdr.lin = lin; mhdr.lout = lout; mhdr.llog = sizeof(log->buf) - 1; iov[0].iov_base = &mhdr; iov[0].iov_len = sizeof(mhdr); iov[1].iov_base = (void *)inbuf; iov[1].iov_len = lin; iov[2].iov_base = log->buf; iov[2].iov_len = 0; l = sendmsg(fd, &msg, 0); if(0 > l) { ERROR("Error sending message to server: %m"); return -1; } if(l != sizeof(mhdr) + lin) { ERROR("Error sending message to server: Partial write"); return -1; } iov[1].iov_base = outbuf; iov[1].iov_len = lout; iov[2].iov_base = log->buf; iov[2].iov_len = sizeof(log->buf); struct pollfd pfd; int timeout = 5000; /* 5 s */ int r; pfd.fd = fd; pfd.events = POLLIN; do { if(0 == (r = poll(&pfd, 1, timeout))) { ERROR("Error getting message from server: Timeout"); return -1; } if(0 > r) { ERROR("Error getting message from server: poll error: %m"); return -1; } }while(0 == (pfd.revents & (POLLERR | POLLHUP | POLLNVAL | POLLIN))); l = recvmsg(fd, &msg, 0); if(0 > l) { ERROR("Error getting message from server: %m"); return -1; } if((sizeof(mhdr) > l) || (l != sizeof(mhdr) + mhdr.lout + mhdr.llog) || (mhdr.cmd != cmd) ) { ERROR("Error getting message from server: Bad format"); return -1; } if(mhdr.lout != lout) { ERROR("Error, unexpected result length %d, expected %d\n", mhdr.lout, lout); return -1; } if(sizeof(log->buf) <= mhdr.llog) { ERROR("Invalid log message length %d", mhdr.llog); return -1; } if(res) *res = mhdr.res; log->buf[mhdr.llog] = 0; return 0; } mstpd-mstpd-8995e5d/ctl_socket_client.h000066400000000000000000000026311516242366700202230ustar00rootroot00000000000000/***************************************************************************** Copyright (c) 2006 EMC Corporation. Copyright (c) 2011 Factor-SPE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. Authors: Srinivas Aji Authors: Vitalii Demianets ******************************************************************************/ #ifndef CTL_SOCKET_CLIENT_H #define CTL_SOCKET_CLIENT_H #include "ctl_functions.h" int send_ctl_message(int cmd, void *inbuf, int lin, void *outbuf, int lout, LogString *log, int *res); int ctl_client_init(void); void ctl_client_cleanup(void); #endif /* CTL_SOCKET_CLIENT_H */ mstpd-mstpd-8995e5d/ctl_socket_server.c000066400000000000000000000233651516242366700202550ustar00rootroot00000000000000/***************************************************************************** Copyright (c) 2006 EMC Corporation. Copyright (c) 2011 Factor-SPE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. Authors: Srinivas Aji Authors: Vitalii Demianets ******************************************************************************/ #include #include #include "ctl_socket_client.h" #include "epoll_loop.h" #include "log.h" static int server_socket(void) { struct sockaddr_un sa; int s, creds; TST(strlen(MSTP_SERVER_SOCK_NAME) < sizeof(sa.sun_path), -1); if(0 > (s = socket(PF_UNIX, SOCK_DGRAM, 0))) { ERROR("Couldn't open unix socket: %m"); return -1; } set_socket_address(&sa, MSTP_SERVER_SOCK_NAME); creds = 1; if(0 != setsockopt(s, SOL_SOCKET, SO_PASSCRED, &creds, sizeof(creds))) { ERROR("Couldn't enable SO_PASSCRED: %m"); close(s); return -1; } if(0 != bind(s, (struct sockaddr *)&sa, sizeof(sa))) { ERROR("Couldn't bind socket: %m"); close(s); return -1; } return s; } static int handle_message(int cmd, void *inbuf, int lin, void *outbuf, int lout) { switch(cmd) { SERVER_MESSAGE_CASE(get_cist_bridge_status); SERVER_MESSAGE_CASE(get_msti_bridge_status); SERVER_MESSAGE_CASE(set_cist_bridge_config); SERVER_MESSAGE_CASE(set_msti_bridge_config); SERVER_MESSAGE_CASE(get_cist_port_status); SERVER_MESSAGE_CASE(get_msti_port_status); SERVER_MESSAGE_CASE(set_cist_port_config); SERVER_MESSAGE_CASE(set_msti_port_config); SERVER_MESSAGE_CASE(port_mcheck); SERVER_MESSAGE_CASE(set_debug_level); SERVER_MESSAGE_CASE(get_mstilist); SERVER_MESSAGE_CASE(create_msti); SERVER_MESSAGE_CASE(delete_msti); SERVER_MESSAGE_CASE(get_mstconfid); SERVER_MESSAGE_CASE(set_mstconfid); SERVER_MESSAGE_CASE(get_vids2fids); SERVER_MESSAGE_CASE(get_fids2mstids); SERVER_MESSAGE_CASE(set_vid2fid); SERVER_MESSAGE_CASE(set_fid2mstid); SERVER_MESSAGE_CASE(set_vids2fids); SERVER_MESSAGE_CASE(set_fids2mstids); case CMD_CODE_add_bridges: { if(0 != lout) { LOG("Bad sizes: lout %d != 0", lout); return -1; } if(sizeof(int) > lin) { LOG("Bad sizes: lin == 0"); return -1; } int *br_array = inbuf; int i, serialized_data_count, chunk_count, brcount = br_array[0]; int *ptr = br_array + (serialized_data_count = (brcount + 1)); if(lin < ((serialized_data_count + 1) * sizeof(int))) { bad_lin1: LOG("Bad sizes: lin %d < %d", lin, (serialized_data_count + 1) * sizeof(int)); return -1; } for(i = 0; i < brcount; ++i) { serialized_data_count += (chunk_count = *ptr + 1); if(i < (brcount - 1)) { if(lin < ((serialized_data_count + 1) * sizeof(int))) goto bad_lin1; ptr += chunk_count; } else { if(lin != (serialized_data_count * sizeof(int))) { LOG("Bad sizes: lin %d != %d", lin, serialized_data_count * sizeof(int)); return -1; } } } int* *ifaces_lists = malloc(brcount * sizeof(int*)); if(NULL == ifaces_lists) { LOG("out of memory, brcount = %d\n", brcount); return -1; } ptr = br_array + (brcount + 1); for(i = 0; i < brcount; ++i) { ifaces_lists[i] = ptr; ptr += ifaces_lists[i][0] + 1; } int r = CTL_add_bridges(br_array, ifaces_lists); free(ifaces_lists); if(r) return r; return r; } case CMD_CODE_del_bridges: { if(0 != lout) { LOG("Bad sizes: lout %d != 0", lout); return -1; } if(sizeof(int) > lin) { LOG("Bad sizes: lin == 0"); return -1; } int *br_array = inbuf; int brcount = br_array[0]; if(((brcount + 1) * sizeof(int)) != lin) { LOG("Bad sizes: lin %d != %d", lin, (brcount + 1) * sizeof(int)); return -1; } int r = CTL_del_bridges(br_array); if(r) return r; return r; } default: ERROR("CTL: Unknown command %d", cmd); return -1; } } int ctl_in_handler = 0; static unsigned char msg_logbuf[LOG_STRING_LEN]; static unsigned int msg_log_offset; void _ctl_err_log(char *fmt, ...) { if((sizeof(msg_logbuf) - 1) <= msg_log_offset) return; int r; va_list ap; va_start(ap, fmt); r = vsnprintf((char *)msg_logbuf + msg_log_offset, sizeof(msg_logbuf) - msg_log_offset, fmt, ap); va_end(ap); msg_log_offset += r; if(sizeof(msg_logbuf) <= msg_log_offset) { msg_log_offset = sizeof(msg_logbuf) - 1; msg_logbuf[sizeof(msg_logbuf) - 1] = 0; } } #define MSG_BUF_LEN 10000 static unsigned char msg_inbuf[MSG_BUF_LEN]; static unsigned char msg_outbuf[MSG_BUF_LEN]; static unsigned char msg_ctlbuf[CMSG_SPACE(sizeof(struct ucred))]; static bool ctl_access_ok(const struct ucred *creds, int cmd) { switch(cmd) { case CMD_CODE_get_cist_bridge_status: case CMD_CODE_get_msti_bridge_status: case CMD_CODE_get_cist_port_status: case CMD_CODE_get_msti_port_status: case CMD_CODE_get_mstilist: case CMD_CODE_get_mstconfid: case CMD_CODE_get_vids2fids: case CMD_CODE_get_fids2mstids: return true; default: return creds->uid == 0; } } static void ctl_rcv_handler(uint32_t events, struct epoll_event_handler *p) { struct ctl_msg_hdr mhdr; struct msghdr msg; struct sockaddr_un sa; struct iovec iov[3]; struct cmsghdr *cmsg; struct ucred *creds; int l; msg.msg_name = &sa; msg.msg_namelen = sizeof(sa); msg.msg_iov = iov; msg.msg_iovlen = 3; msg.msg_control = msg_ctlbuf; msg.msg_controllen = sizeof(msg_ctlbuf); iov[0].iov_base = &mhdr; iov[0].iov_len = sizeof(mhdr); iov[1].iov_base = msg_inbuf; iov[1].iov_len = MSG_BUF_LEN; iov[2].iov_base = NULL; iov[2].iov_len = 0; l = recvmsg(p->fd, &msg, MSG_NOSIGNAL | MSG_DONTWAIT); TST(l > 0,); if((0 != msg.msg_flags) || (sizeof(mhdr) > l) || (l != sizeof(mhdr) + mhdr.lin) || (MSG_BUF_LEN < mhdr.lout) || (0 > mhdr.cmd) ) { ERROR("CTL: Unexpected message. Ignoring"); return; } cmsg = CMSG_FIRSTHDR(&msg); if(!cmsg || (cmsg->cmsg_level != SOL_SOCKET) || (cmsg->cmsg_type != SCM_CREDENTIALS) || (cmsg->cmsg_len != CMSG_LEN(sizeof(struct ucred))) ) { ERROR("CTL: No creds or unexpected control message. Ignoring"); return; } creds = (struct ucred *)CMSG_DATA(cmsg); msg_log_offset = 0; ctl_in_handler = 1; if(ctl_access_ok(creds, mhdr.cmd)) { if(!(mhdr.cmd & RESPONSE_FIRST_HANDLE_LATER)) mhdr.res = handle_message(mhdr.cmd, msg_inbuf, mhdr.lin, msg_outbuf, mhdr.lout); else mhdr.res = 0; } else { ERROR("Operation not permitted"); mhdr.res = -1; } ctl_in_handler = 0; if(0 > mhdr.res) memset(msg_outbuf, 0, mhdr.lout); if(msg_log_offset < mhdr.llog) mhdr.llog = msg_log_offset; iov[1].iov_base = msg_outbuf; iov[1].iov_len = mhdr.lout; iov[2].iov_base = msg_logbuf; iov[2].iov_len = mhdr.llog; l = sendmsg(p->fd, &msg, MSG_NOSIGNAL); if(0 > l) ERROR("CTL: Couldn't send response: %m"); else if(l != sizeof(mhdr) + mhdr.lout + mhdr.llog) { ERROR ("CTL: Couldn't send full response, sent %d bytes instead of %zd.", l, sizeof(mhdr) + mhdr.lout + mhdr.llog); } if(mhdr.res == 0 && mhdr.cmd & RESPONSE_FIRST_HANDLE_LATER) handle_message(mhdr.cmd, msg_inbuf, mhdr.lin, msg_outbuf, mhdr.lout); } static struct epoll_event_handler ctl_handler = {0}; int ctl_socket_init(void) { int s = server_socket(); if(0 > s) return -1; ctl_handler.fd = s; ctl_handler.handler = ctl_rcv_handler; TST(add_epoll(&ctl_handler) == 0, -1); return 0; } void ctl_socket_cleanup(void) { remove_epoll(&ctl_handler); close(ctl_handler.fd); } mstpd-mstpd-8995e5d/ctl_socket_server.h000066400000000000000000000026141516242366700202540ustar00rootroot00000000000000/***************************************************************************** Copyright (c) 2006 EMC Corporation. Copyright (c) 2011 Factor-SPE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. Authors: Srinivas Aji Authors: Vitalii Demianets ******************************************************************************/ #ifndef CTL_SOCKET_SERVER_H #define CTL_SOCKET_SERVER_H int ctl_socket_init(void); void ctl_socket_cleanup(void); extern int ctl_in_handler; void _ctl_err_log(char *fmt, ...); #define ctl_err_log(_fmt...) ({ if (ctl_in_handler) _ctl_err_log(_fmt); }) #endif /* CTL_SOCKET_SERVER_H */ mstpd-mstpd-8995e5d/debian/000077500000000000000000000000001516242366700156025ustar00rootroot00000000000000mstpd-mstpd-8995e5d/debian/README.md000066400000000000000000000002021516242366700170530ustar00rootroot00000000000000# Packaging for Debian The packaging for Debian has been removed from this branch. It is now located in the `debian/sid` branch. mstpd-mstpd-8995e5d/driver.h000066400000000000000000000017021516242366700160240ustar00rootroot00000000000000/* * driver.h Driver-specific code. * * This program is free software; you can redistribute it and/or * modify it under the terms of the 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: Vitalii Demianets */ #ifndef _MSTP_DRIVER_H #define _MSTP_DRIVER_H int driver_set_new_state(per_tree_port_t *ptp, int new_state); void driver_flush_all_fids(per_tree_port_t *ptp); unsigned int driver_set_ageing_time(port_t *prt, unsigned int ageingTime); bool driver_create_msti(bridge_t *br, __u16 mstid); bool driver_delete_msti(bridge_t *br, __u16 mstid); int driver_mstp_init(); void driver_mstp_fini(); bool driver_create_bridge(bridge_t *br, __u8 *macaddr); bool driver_create_port(port_t *prt, __u16 portno); void driver_delete_bridge(bridge_t *br); void driver_delete_port(port_t *prt); #endif /* _MSTP_DRIVER_H */ mstpd-mstpd-8995e5d/driver_deps.c000066400000000000000000000041651516242366700170400ustar00rootroot00000000000000/* * driver_deps.c Driver-specific code. * * This program is free software; you can redistribute it and/or * modify it under the terms of the 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: Vitalii Demianets */ #include #include #include #include #include "log.h" #include "mstp.h" /* Initialize driver objects & states */ int driver_mstp_init() { return 0; } /* Cleanup driver objects & states */ void driver_mstp_fini() { } /* Driver hook that is called before a bridge is created */ bool driver_create_bridge(bridge_t *br, __u8 *macaddr) { return true; } /* Driver hook that is called before a port is created */ bool driver_create_port(port_t *prt, __u16 portno) { return true; } /* Driver hook that is called when a bridge is deleted */ void driver_delete_bridge(bridge_t *br) { } /* Driver hook that is called when a port is deleted */ void driver_delete_port(port_t *prt) { } /* * Set new state (BR_STATE_xxx) for the given port and MSTI. * Return new actual state (BR_STATE_xxx) from driver. */ int driver_set_new_state(per_tree_port_t *ptp, int new_state) { /* TODO: insert driver-specific code here */ return new_state; } bool driver_create_msti(bridge_t *br, __u16 mstid) { /* TODO: send "create msti" command to driver */ return true; } bool driver_delete_msti(bridge_t *br, __u16 mstid) { /* TODO: send "delete msti" command to driver */ return true; } void driver_flush_all_fids(per_tree_port_t *ptp) { /* TODO: do real flushing. * Make it asynchronous, with completion function calling * MSTP_IN_all_fids_flushed(ptp) */ MSTP_IN_all_fids_flushed(ptp); } /* * Set new ageing time (in seconds) for the port. * Return new actual ageing time from driver (the ageing timer granularity * in the hardware can be more than 1 sec) */ unsigned int driver_set_ageing_time(port_t *prt, unsigned int ageingTime) { /* TODO: do set new ageing time */ return ageingTime; } mstpd-mstpd-8995e5d/epoll_loop.c000066400000000000000000000100121516242366700166620ustar00rootroot00000000000000/***************************************************************************** Copyright (c) 2006 EMC Corporation. Copyright (c) 2011 Factor-SPE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. Authors: Srinivas Aji Authors: Vitalii Demianets ******************************************************************************/ #include #include #include #include #include "log.h" #include "epoll_loop.h" #include "bridge_ctl.h" #include "clock_gettime.h" /* globals */ static int epoll_fd = -1; static struct timespec nexttimeout; int init_epoll(void) { int r = epoll_create(128); if(r < 0) { ERROR("epoll_create failed: %m\n"); return -1; } epoll_fd = r; return 0; } int add_epoll(struct epoll_event_handler *h) { struct epoll_event ev = { .events = EPOLLIN, .data.ptr = h, }; h->ref_ev = NULL; int r = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, h->fd, &ev); if(r < 0) { ERROR("epoll_ctl_add: %m\n"); return -1; } return 0; } int remove_epoll(struct epoll_event_handler *h) { int r = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, h->fd, NULL); if(r < 0) { ERROR("epoll_ctl_del: %m\n"); return -1; } if(h->ref_ev && h->ref_ev->data.ptr == h) { h->ref_ev->data.ptr = NULL; h->ref_ev = NULL; } return 0; } void clear_epoll(void) { if(epoll_fd >= 0) close(epoll_fd); } static inline int time_diff(struct timespec *second, struct timespec *first) { return (second->tv_sec - first->tv_sec) * 1000 + (second->tv_nsec - first->tv_nsec) / 1000000; } static inline void run_timeouts(void) { bridge_one_second(); ++(nexttimeout.tv_sec); } int epoll_main_loop(volatile bool *quit) { clock_gettime(CLOCK_MONOTONIC, &nexttimeout); ++(nexttimeout.tv_sec); #define EV_SIZE 8 struct epoll_event ev[EV_SIZE]; while(!*quit) { int r, i; int timeout; struct timespec tv; clock_gettime(CLOCK_MONOTONIC, &tv); timeout = time_diff(&nexttimeout, &tv); if(timeout < 0 || timeout > 1000) { run_timeouts(); /* * Check if system time has changed. */ if(timeout < -4000 || timeout > 1000) { /* Most probably, system time has changed */ nexttimeout.tv_nsec = tv.tv_nsec; nexttimeout.tv_sec = tv.tv_sec + 1; } timeout = 0; } r = epoll_wait(epoll_fd, ev, EV_SIZE, timeout); if(r < 0 && errno != EINTR) { ERROR("epoll_wait: %m\n"); return -1; } for(i = 0; i < r; ++i) { struct epoll_event_handler *p = ev[i].data.ptr; if(p != NULL) p->ref_ev = &ev[i]; } for (i = 0; i < r; ++i) { struct epoll_event_handler *p = ev[i].data.ptr; if(p && p->handler) p->handler(ev[i].events, p); } for (i = 0; i < r; ++i) { struct epoll_event_handler *p = ev[i].data.ptr; if(p != NULL) p->ref_ev = NULL; } } return 0; } mstpd-mstpd-8995e5d/epoll_loop.h000066400000000000000000000033131516242366700166750ustar00rootroot00000000000000/***************************************************************************** Copyright (c) 2006 EMC Corporation. Copyright (c) 2011 Factor-SPE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. Authors: Srinivas Aji Authors: Vitalii Demianets ******************************************************************************/ #ifndef EPOLL_LOOP_H #define EPOLL_LOOP_H #include #include #include struct epoll_event_handler { int fd; void *arg; void (*handler) (uint32_t events, struct epoll_event_handler * p); struct epoll_event *ref_ev; /* if set, epoll loop has reference to this, so mark that ref as NULL while freeing */ }; int init_epoll(void); void clear_epoll(void); int epoll_main_loop(volatile bool *quit); int add_epoll(struct epoll_event_handler *h); int remove_epoll(struct epoll_event_handler *h); #endif /* EPOLL_LOOP_H */ mstpd-mstpd-8995e5d/hmac_md5.c000066400000000000000000000416351516242366700162120ustar00rootroot00000000000000/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All rights reserved. License to copy and use this software is granted provided that it is identified as the "RSA Data Security, Inc. MD5 Message-Digest Algorithm" in all material mentioning or referencing this software or this function. License is also granted to make and use derivative works provided that such works are identified as "derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm" in all material mentioning or referencing the derived work. RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided "as is" without express or implied warranty of any kind. These notices must be retained in any copies of any part of this documentation and/or software. */ #include #include #include #include "mstp.h" /* POINTER defines a generic pointer type */ typedef unsigned char *POINTER; /* UINT2 defines a two byte word */ typedef __u16 UINT2; /* UINT4 defines a four byte word */ typedef __u32 UINT4; /* MD5 context. */ typedef struct { UINT4 state[4]; /* state (ABCD) */ UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ unsigned char buffer[64]; /* input buffer */ } MD5_CTX; #define MD5_memcpy(output, input, len) memcpy((output), (input), (len)) #define MD5_memset(output, value, len) memset((output), (value), (len)) /* Constants for MD5Transform routine. */ #define S11 7 #define S12 12 #define S13 17 #define S14 22 #define S21 5 #define S22 9 #define S23 14 #define S24 20 #define S31 4 #define S32 11 #define S33 16 #define S34 23 #define S41 6 #define S42 10 #define S43 15 #define S44 21 static const unsigned char PADDING[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 }; /* F, G, H and I are basic MD5 functions. */ #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))) /* ROTATE_LEFT rotates x left n bits. */ #define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) /* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. Rotation is separate from addition to prevent recomputation. */ #define FF(a, b, c, d, x, s, ac) { \ (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ (a) = ROTATE_LEFT ((a), (s)); \ (a) += (b); \ } #define GG(a, b, c, d, x, s, ac) { \ (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ (a) = ROTATE_LEFT ((a), (s)); \ (a) += (b); \ } #define HH(a, b, c, d, x, s, ac) { \ (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ (a) = ROTATE_LEFT ((a), (s)); \ (a) += (b); \ } #define II(a, b, c, d, x, s, ac) { \ (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ (a) = ROTATE_LEFT ((a), (s)); \ (a) += (b); \ } /* Encodes input (UINT4) into output (unsigned char). Assumes len is a multiple of 4. */ static void Encode(unsigned char *output, const UINT4 *input, unsigned int len) { unsigned int i, j; for(i = 0, j = 0; j < len; i++, j += 4) { output[j] = (unsigned char)(input[i] & 0xff); output[j + 1] = (unsigned char)((input[i] >> 8) & 0xff); output[j + 2] = (unsigned char)((input[i] >> 16) & 0xff); output[j + 3] = (unsigned char)((input[i] >> 24) & 0xff); } } /* Decodes input (unsigned char) into output (UINT4). Assumes len is a multiple of 4. */ static void Decode(UINT4 *output, const unsigned char *input, unsigned int len) { unsigned int i, j; for(i = 0, j = 0; j < len; i++, j += 4) output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); } /* MD5 basic transformation. Transforms state based on block. */ static void MD5Transform(UINT4 state[4], const unsigned char block[64]) { UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; Decode(x, block, 64); /* Round 1 */ FF(a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ FF(d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ FF(c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ FF(b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ FF(a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ FF(d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ FF(c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ FF(b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ FF(a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ FF(d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ /* Round 2 */ GG(a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ GG(d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ GG(b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ GG(a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */ GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ GG(b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ GG(a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ GG(c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ GG(b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ GG(d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ GG(c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ /* Round 3 */ HH(a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ HH(d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ HH(a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ HH(d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ HH(c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ HH(d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ HH(c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ HH(b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ HH(a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ HH(b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ /* Round 4 */ II(a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ II(d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ II(b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ II(d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ II(b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ II(a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ II(c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ II(a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ II(c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ II(b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ state[0] += a; state[1] += b; state[2] += c; state[3] += d; /* Zeroize sensitive information. */ /* No need in MSTP ;) */ /* MD5_memset((POINTER)x, 0, sizeof (x)); */ } /* MD5 initialization. Begins an MD5 operation, writing a new context. */ static void MD5Init(MD5_CTX *context) { context->count[0] = context->count[1] = 0; /* Load magic initialization constants. */ context->state[0] = 0x67452301; context->state[1] = 0xefcdab89; context->state[2] = 0x98badcfe; context->state[3] = 0x10325476; } /* MD5 block update operation. Continues an MD5 message-digest operation, processing another message block, and updating the context. */ static void MD5Update(MD5_CTX *context, const unsigned char *input, unsigned int inputLen) { unsigned int i, index, partLen; /* Compute number of bytes mod 64 */ index = (unsigned int)((context->count[0] >> 3) & 0x3F); /* Update number of bits */ if((context->count[0] += ((UINT4)inputLen << 3)) < ((UINT4)inputLen << 3)) context->count[1]++; context->count[1] += ((UINT4)inputLen >> 29); partLen = 64 - index; /* Transform as many times as possible. */ if(inputLen >= partLen) { MD5_memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen); MD5Transform(context->state, context->buffer); for(i = partLen; i + 63 < inputLen; i += 64) MD5Transform(context->state, &input[i]); index = 0; } else i = 0; /* Buffer remaining input */ MD5_memcpy((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen - i); } /* MD5 finalization. Ends an MD5 message-digest operation, writing the the message digest and zeroizing the context. */ static void MD5Final(unsigned char digest[16], MD5_CTX *context) { unsigned char bits[8]; unsigned int index, padLen; /* Save number of bits */ Encode(bits, context->count, 8); /* Pad out to 56 mod 64. */ index = (unsigned int)((context->count[0] >> 3) & 0x3f); padLen = (index < 56) ? (56 - index) : (120 - index); MD5Update(context, PADDING, padLen); /* Append length (before padding) */ MD5Update(context, bits, 8); /* Store state in digest */ Encode(digest, context->state, 16); /* Zeroize sensitive information. */ /* No need in MSTP ;) */ /* MD5_memset((POINTER)context, 0, sizeof(*context)); */ } /* ** Function: hmac_md5 from RFC-2104 */ void hmac_md5(const unsigned char* text, int text_len, const unsigned char* key, int key_len, void* 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(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(digest, &context); /* finish up 2nd pass */ } #ifdef HMAC_MDS_TEST_FUNCTIONS /* Digests a string */ static void MD5String(const char *string, void *digest) { MD5_CTX context; unsigned int len = strlen(string); MD5Init(&context); MD5Update(&context, (const unsigned char *)string, len); MD5Final(digest, &context); } /* Digests a reference suite of strings */ bool MD5TestSuite(void) { unsigned char digest[16]; unsigned char key[16]; unsigned char mstp_key[16] = HMAC_KEY; unsigned char data[4096 * 2]; int i; /* Tests from RFC-1231 */ MD5String("", digest); { unsigned char expected_result[16] = { 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e}; if(memcmp(expected_result, digest, 16)) return false; } MD5String("a", digest); { unsigned char expected_result[16] = { 0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61}; if(memcmp(expected_result, digest, 16)) return false; } MD5String("abc", digest); { unsigned char expected_result[16] = { 0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1, 0x7f, 0x72}; if(memcmp(expected_result, digest, 16)) return false; } MD5String("message digest", digest); { unsigned char expected_result[16] = { 0xf9, 0x6b, 0x69, 0x7d, 0x7c, 0xb7, 0x93, 0x8d, 0x52, 0x5a, 0x2f, 0x31, 0xaa, 0xf1, 0x61, 0xd0}; if(memcmp(expected_result, digest, 16)) return false; } MD5String("abcdefghijklmnopqrstuvwxyz", digest); { unsigned char expected_result[16] = { 0xc3, 0xfc, 0xd3, 0xd7, 0x61, 0x92, 0xe4, 0x00, 0x7d, 0xfb, 0x49, 0x6c, 0xca, 0x67, 0xe1, 0x3b}; if(memcmp(expected_result, digest, 16)) return false; } MD5String("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", digest); { unsigned char expected_result[16] = { 0xd1, 0x74, 0xab, 0x98, 0xd2, 0x77, 0xd9, 0xf5, 0xa5, 0x61, 0x1c, 0x2c, 0x9f, 0x41, 0x9d, 0x9f}; if(memcmp(expected_result, digest, 16)) return false; } MD5String("1234567890123456789012345678901234567890" "1234567890123456789012345678901234567890", digest); { unsigned char expected_result[16] = { 0x57, 0xed, 0xf4, 0xa2, 0x2b, 0xe3, 0xc9, 0x55, 0xac, 0x49, 0xda, 0x2e, 0x21, 0x07, 0xb6, 0x7a}; if(memcmp(expected_result, digest, 16)) return false; } /* Tests from RFC-2104 */ memset(key, 0x0B, 16); hmac_md5((unsigned char *)"Hi There", 8, key, 16, (void *)digest); { unsigned char expected_result[16] = { 0x92, 0x94, 0x72, 0x7a, 0x36, 0x38, 0xbb, 0x1c, 0x13, 0xf4, 0x8e, 0xf8, 0x15, 0x8b, 0xfc, 0x9d}; if(memcmp(expected_result, digest, 16)) return false; } hmac_md5((unsigned char *)"what do ya want for nothing?", 28, (unsigned char *)"Jefe", 4, (void *)digest); { unsigned char expected_result[16] = { 0x75, 0x0c, 0x78, 0x3e, 0x6a, 0xb0, 0xb5, 0x03, 0xea, 0xa8, 0x6e, 0x31, 0x0a, 0x5d, 0xb7, 0x38}; if(memcmp(expected_result, digest, 16)) return false; } memset(key, 0xAA, 16); memset(data, 0xDD, 50); hmac_md5(data, 50, key, 16, (void*)digest); { unsigned char expected_result[16] = { 0x56, 0xbe, 0x34, 0x52, 0x1d, 0x14, 0x4c, 0x88, 0xdb, 0xb8, 0xc7, 0x33, 0xf0, 0xe8, 0xb3, 0xf6}; if(memcmp(expected_result, digest, 16)) return false; } /* Tests from IEEE 802.1Q-2005 13.7 Table 13-2 */ memset(data, 0, 4096 * 2); hmac_md5(data, 4096 * 2, mstp_key, 16, (void *)digest); { unsigned char expected_result[16] = { 0xac, 0x36, 0x17, 0x7f, 0x50, 0x28, 0x3c, 0xd4, 0xb8, 0x38, 0x21, 0xd8, 0xab, 0x26, 0xde, 0x62}; if(memcmp(expected_result, digest, 16)) return false; } for(i = 3; i < 4095 * 2; i+= 2) data[i] = 1; hmac_md5(data, 4096 * 2, mstp_key, 16, (void *)digest); { unsigned char expected_result[16] = { 0xe1, 0x3a, 0x80, 0xf1, 0x1e, 0xd0, 0x85, 0x6a, 0xcd, 0x4e, 0xe3, 0x47, 0x69, 0x41, 0xc7, 0x3b}; if(memcmp(expected_result, digest, 16)) return false; } for(i = 3; i < 4095 * 2; i+= 2) data[i] = (i / 2) % 32 + 1; hmac_md5(data, 4096 * 2, mstp_key, 16, (void *)digest); { unsigned char expected_result[16] = { 0x9d, 0x14, 0x5c, 0x26, 0x7d, 0xbe, 0x9f, 0xb5, 0xd8, 0x93, 0x44, 0x1b, 0xe3, 0xba, 0x08, 0xce}; if(memcmp(expected_result, digest, 16)) return false; } return true; } #endif /* HMAC_MDS_TEST_FUNCTIONS */ mstpd-mstpd-8995e5d/libnetlink.c000066400000000000000000000351321516242366700166630ustar00rootroot00000000000000/* * 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, * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "libnetlink.h" #ifndef DEFAULT_RTNL_BUFSIZE #define DEFAULT_RTNL_BUFSIZE 256 * 1024 #endif #ifndef RTNL_SND_BUFSIZE #define RTNL_SND_BUFSIZE DEFAULT_RTNL_BUFSIZE #endif #ifndef RTNL_RCV_BUFSIZE #define RTNL_RCV_BUFSIZE DEFAULT_RTNL_BUFSIZE #endif void rtnl_close(struct rtnl_handle *rth) { close(rth->fd); } int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, int protocol) { socklen_t addr_len; int sndbuf = RTNL_SND_BUFSIZE; int rcvbuf = RTNL_RCV_BUFSIZE; memset(rth, 0, sizeof(*rth)); rth->fd = socket(AF_NETLINK, SOCK_RAW, protocol); if (rth->fd < 0) { ERROR("Cannot open netlink socket"); return -1; } if (setsockopt(rth->fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0) { ERROR("SO_SNDBUF"); goto err_close; } if (setsockopt(rth->fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) < 0) { ERROR("SO_RCVBUF"); goto err_close; } 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) { ERROR("Cannot bind netlink socket"); goto err_close; } addr_len = sizeof(rth->local); if (getsockname(rth->fd, (struct sockaddr *)&rth->local, &addr_len) < 0) { ERROR("Cannot getsockname"); goto err_close; } if (addr_len != sizeof(rth->local)) { ERROR("Wrong address length %d\n", addr_len); goto err_close; } if (rth->local.nl_family != AF_NETLINK) { ERROR("Wrong address family %d\n", rth->local.nl_family); goto err_close; } rth->seq = time(NULL); return 0; err_close: close(rth->fd); return -1; } 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; struct sockaddr_nl nladdr; memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; 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 sendto(rth->fd, (void *)&req, sizeof(req), 0, (struct sockaddr *)&nladdr, sizeof(nladdr)); } int rtnl_send(struct rtnl_handle *rth, const char *buf, int len) { struct sockaddr_nl nladdr; memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; return sendto(rth->fd, buf, len, 0, (struct sockaddr *)&nladdr, sizeof(nladdr)); } 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(struct rtnl_handle *rth, rtnl_filter_t filter, void *arg1, rtnl_filter_t junk, void *arg2) { 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; struct nlmsghdr *h; iov.iov_len = sizeof(buf); status = recvmsg(rth->fd, &msg, 0); if (status < 0) { if (errno == EINTR) continue; ERROR("OVERRUN"); continue; } if (status == 0) { ERROR("EOF on netlink\n"); return -1; } h = (struct nlmsghdr *)buf; while (NLMSG_OK(h, status)) { int err; if (nladdr.nl_pid != 0 || h->nlmsg_pid != rth->local.nl_pid || h->nlmsg_seq != rth->dump) { if (junk) { err = junk(&nladdr, h, arg2); if (err < 0) return err; } goto skip_it; } if (h->nlmsg_type == NLMSG_DONE) return 0; if (h->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h); if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) { ERROR("ERROR truncated\n"); } else { errno = -err->error; ERROR("RTNETLINK answers"); } return -1; } err = filter(&nladdr, h, arg1); if (err < 0) return err; skip_it: h = NLMSG_NEXT(h, status); } if (msg.msg_flags & MSG_TRUNC) { ERROR("Message truncated\n"); continue; } if (status) { ERROR("!!!Remnant of size %d\n", status); return -1; } } } 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) { ERROR("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) continue; ERROR("OVERRUN"); continue; } if (status == 0) { ERROR("EOF on netlink\n"); return -1; } if (msg.msg_namelen != sizeof(nladdr)) { ERROR("sender address length == %d\n", msg.msg_namelen); return -1; } for (h = (struct nlmsghdr *)buf; status >= sizeof(*h);) { int err; int len = h->nlmsg_len; int l = len - sizeof(*h); if (l < 0 || len > status) { if (msg.msg_flags & MSG_TRUNC) { ERROR("Truncated message\n"); return -1; } ERROR( "!!!malformed message: len=%d\n", len); return -1; } if (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 *err = (struct nlmsgerr *)NLMSG_DATA(h); if (l < sizeof(struct nlmsgerr)) { ERROR("ERROR truncated\n"); } else { errno = -err->error; if (errno == 0) { if (answer) memcpy(answer, h, h->nlmsg_len); return 0; } ERROR("RTNETLINK answers"); } return -1; } if (answer) { memcpy(answer, h, h->nlmsg_len); return 0; } ERROR("Unexpected reply!!!\n"); status -= NLMSG_ALIGN(len); h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len)); } if (msg.msg_flags & MSG_TRUNC) { ERROR("Message truncated\n"); continue; } if (status) { ERROR("!!!Remnant of size %d\n", status); return -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) continue; if (errno == EAGAIN) return 0; ERROR("OVERRUN: recvmsg(): error %d : %s\n", errno, strerror(errno)); return -1; } if (status == 0) { ERROR("EOF on netlink\n"); return -1; } if (msg.msg_namelen != sizeof(nladdr)) { ERROR("Sender address length == %d\n", msg.msg_namelen); return -1; } for (h = (struct nlmsghdr *)buf; status >= sizeof(*h);) { int err; int len = h->nlmsg_len; int l = len - sizeof(*h); if (l < 0 || len > status) { if (msg.msg_flags & MSG_TRUNC) { ERROR("Truncated message\n"); return -1; } ERROR( "!!!malformed message: len=%d\n", len); return -1; } err = handler(&nladdr, h, jarg); if (err < 0) { ERROR("Handler returned %d\n", err); return err; } status -= NLMSG_ALIGN(len); h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len)); } if (msg.msg_flags & MSG_TRUNC) { ERROR("Message truncated\n"); continue; } if (status) { ERROR("!!!Remnant of size %d\n", status); return -1; } } } int rtnl_from_file(FILE * rtnl, rtnl_filter_t handler, void *jarg) { int status; 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, len; int l; status = fread(&buf, 1, sizeof(*h), rtnl); if (status < 0) { if (errno == EINTR) continue; ERROR("rtnl_from_file: fread"); return -1; } if (status == 0) return 0; len = h->nlmsg_len; l = len - sizeof(*h); if (l < 0 || len > sizeof(buf)) { ERROR("!!!malformed message: len=%d @%lu\n", len, ftell(rtnl)); return -1; } status = fread(NLMSG_DATA(h), 1, NLMSG_ALIGN(l), rtnl); if (status < 0) { ERROR("rtnl_from_file: fread"); return -1; } if (status < l) { ERROR("rtnl-from_file: truncated message\n"); 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 (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { ERROR( "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 addattr8(struct nlmsghdr *n, int maxlen, int type, __u8 data) { int len = RTA_LENGTH(1); struct rtattr *rta; if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { ERROR( "addattr8: 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, 1); 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 (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { ERROR( "addattr_l ERROR: message exceeded bound of %d\n", maxlen); return -1; } rta = NLMSG_TAIL(n); rta->rta_type = type; rta->rta_len = len; memcpy(RTA_DATA(rta), data, alen); n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); return 0; } int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len) { if (NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len) > maxlen) { ERROR( "addraw_l ERROR: message exceeded bound of %d\n", maxlen); return -1; } memcpy(NLMSG_TAIL(n), data, len); memset((void *)NLMSG_TAIL(n) + len, 0, NLMSG_ALIGN(len) - len); n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len); return 0; } int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data) { int len = RTA_LENGTH(4); struct rtattr *subrta; if (RTA_ALIGN(rta->rta_len) + len > maxlen) { ERROR( "rta_addattr32: Error! max allowed bound %d exceeded\n", maxlen); return -1; } subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len)); subrta->rta_type = type; subrta->rta_len = len; memcpy(RTA_DATA(subrta), &data, 4); rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len; return 0; } int rta_addattr_l(struct rtattr *rta, int maxlen, int type, const void *data, int alen) { struct rtattr *subrta; int len = RTA_LENGTH(alen); if (RTA_ALIGN(rta->rta_len) + RTA_ALIGN(len) > maxlen) { ERROR( "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 rta_addattr8(struct rtattr *rta, int maxlen, int type, __u8 data) { return rta_addattr_l(rta, maxlen, type, &data, sizeof(__u8)); } int rta_addattr16(struct rtattr *rta, int maxlen, int type, __u16 data) { return rta_addattr_l(rta, maxlen, type, &data, sizeof(__u16)); } int rta_addattr64(struct rtattr *rta, int maxlen, int type, __u64 data) { return rta_addattr_l(rta, maxlen, type, &data, sizeof(__u64)); } struct rtattr *rta_nest(struct rtattr *rta, int maxlen, int type) { struct rtattr *nest = RTA_TAIL(rta); rta_addattr_l(rta, maxlen, type, NULL, 0); nest->rta_type |= NLA_F_NESTED; return nest; } int rta_nest_end(struct rtattr *rta, struct rtattr *nest) { nest->rta_len = (void *)RTA_TAIL(rta) - (void *)nest; return rta->rta_len; } int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) { memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); while (RTA_OK(rta, len)) { if (rta->rta_type <= max) tb[rta->rta_type] = rta; rta = RTA_NEXT(rta, len); } if (len) ERROR("!!!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) ERROR("!!!Deficit %d, rta_len=%d\n", len, rta->rta_len); return i; } mstpd-mstpd-8995e5d/libnetlink.h000066400000000000000000000051521516242366700166670ustar00rootroot00000000000000#ifndef __LIBNETLINK_H__ #define __LIBNETLINK_H__ #include #include #include #include #include struct rtnl_handle { int fd; struct sockaddr_nl local; struct sockaddr_nl peer; __u32 seq; __u32 dump; }; int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions); int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, int protocol); void rtnl_close(struct rtnl_handle *rth); int rtnl_wilddump_request(struct rtnl_handle *rth, int fam, int type); 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 *); int rtnl_dump_filter(struct rtnl_handle *rth, rtnl_filter_t filter, void *arg1, rtnl_filter_t junk, void *arg2); 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 rtnl_send(struct rtnl_handle *rth, const char *buf, int); int addattr8(struct nlmsghdr *n, int maxlen, int type, __u8 data); int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data); int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen); int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len); int rta_addattr8(struct rtattr *rta, int maxlen, int type, __u8 data); int rta_addattr16(struct rtattr *rta, int maxlen, int type, __u16 data); int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data); int rta_addattr64(struct rtattr *rta, int maxlen, int type, __u64 data); int rta_addattr_l(struct rtattr *rta, int maxlen, int type, const void *data, int alen); int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len); int parse_rtattr_byindex(struct rtattr *tb[], int max, struct rtattr *rta, int len); struct rtattr *rta_nest(struct rtattr *rta, int maxlen, int type); int rta_nest_end(struct rtattr *rta, struct rtattr *nest); #define RTA_TAIL(rta) \ ((struct rtattr *) (((void *) (rta)) + \ RTA_ALIGN((rta)->rta_len))) #define parse_rtattr_nested(tb, max, rta) \ (parse_rtattr((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta))) int rtnl_listen(struct rtnl_handle *, rtnl_filter_t handler, void *jarg); int rtnl_from_file(FILE *, rtnl_filter_t handler, void *jarg); #define NLMSG_TAIL(nmsg) \ ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) #endif /* __LIBNETLINK_H__ */ mstpd-mstpd-8995e5d/list.h000066400000000000000000000351121516242366700155060ustar00rootroot00000000000000/** * * I grub it from linux kernel source code and fix it for user space * program. Of course, this is a GPL licensed header file. * * Here is a recipe to cook list.h for user space program * * 1. copy list.h from linux/include/list.h * 2. remove * - #ifdef __KERNE__ and its #endif * - all #include line * - prefetch() and rcu related functions * 3. add macro offsetof() and container_of * * - kazutomo@mcs.anl.gov */ #ifndef _LINUX_LIST_H #define _LINUX_LIST_H /** * @name from other kernel headers */ /*@{*/ /** * Get offset of a member */ #ifndef offsetof #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #endif /** * Casts a member of a structure out to the containing structure * @param ptr the pointer to the member. * @param type the type of the container struct this is embedded in. * @param member the name of the member within the struct. * */ #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) /*@}*/ /* * These are non-NULL pointers that will result in page faults * under normal circumstances, used to verify that nobody uses * non-initialized list entries. */ #define LIST_POISON1 ((void *) 0x00100100) #define LIST_POISON2 ((void *) 0x00200200) /** * Simple doubly linked list implementation. * * Some of the internal functions ("__xxx") are useful when * manipulating whole lists rather than single entries, as * sometimes we already know the next/prev entries and we can * generate better code by using them directly rather than * using the generic single-entry routines. */ struct list_head { struct list_head *next, *prev; }; #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) #define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ } while (0) /* * Insert a new entry between two known consecutive entries. * * This is only for internal list manipulation where we know * the prev/next entries already! */ 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; } /** * list_add - add a new entry * @new: new entry to be added * @head: list head to add it after * * Insert a new entry after the specified head. * This is good for implementing stacks. */ static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } /** * list_add_tail - add a new entry * @new: new entry to be added * @head: list head to add it before * * Insert a new entry before the specified head. * This is useful for implementing queues. */ static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } /* * Delete a list entry by making the prev/next entries * point to each other. * * This is only for internal list manipulation where we know * the prev/next entries already! */ static inline void __list_del(struct list_head * prev, struct list_head * next) { next->prev = prev; prev->next = next; } /** * list_del - deletes entry from list. * @entry: the element to delete from the list. * Note: list_empty on entry does not return true after this, the entry is * in an undefined state. */ static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; } /** * list_del_init - deletes entry from list and reinitialize it. * @entry: the element to delete from the list. */ static inline void list_del_init(struct list_head *entry) { __list_del(entry->prev, entry->next); INIT_LIST_HEAD(entry); } /** * list_move - delete from one list and add as another's head * @list: the entry to move * @head: the head that will precede our entry */ static inline void list_move(struct list_head *list, struct list_head *head) { __list_del(list->prev, list->next); list_add(list, head); } /** * list_move_tail - delete from one list and add as another's tail * @list: the entry to move * @head: the head that will follow our entry */ static inline void list_move_tail(struct list_head *list, struct list_head *head) { __list_del(list->prev, list->next); list_add_tail(list, head); } /** * list_empty - tests whether a list is empty * @head: the list to test. */ static inline int list_empty(const struct list_head *head) { return head->next == head; } static inline void __list_splice(struct list_head *list, struct list_head *head) { struct list_head *first = list->next; struct list_head *last = list->prev; struct list_head *at = head->next; first->prev = head; head->next = first; last->next = at; at->prev = last; } /** * list_splice - join two lists * @list: the new list to add. * @head: the place to add it in the first list. */ static inline void list_splice(struct list_head *list, struct list_head *head) { if (!list_empty(list)) __list_splice(list, head); } /** * list_splice_init - join two lists and reinitialise the emptied list. * @list: the new list to add. * @head: the place to add it in the first list. * * The list at @list is reinitialised */ static inline void list_splice_init(struct list_head *list, struct list_head *head) { if (!list_empty(list)) { __list_splice(list, head); INIT_LIST_HEAD(list); } } /** * list_entry - get the struct for this entry * @ptr: the &struct list_head pointer. * @type: the type of the struct this is embedded in. * @member: the name of the list_struct within the struct. */ #define list_entry(ptr, type, member) \ container_of(ptr, type, member) /** * list_for_each - iterate over a list * @pos: the &struct list_head to use as a loop counter. * @head: the head for your list. */ #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); \ pos = pos->next) /** * __list_for_each - iterate over a list * @pos: the &struct list_head to use as a loop counter. * @head: the head for your list. * * This variant differs from list_for_each() in that it's the * simplest possible list iteration code, no prefetching is done. * Use this for code that knows the list to be very short (empty * or 1 entry) most of the time. */ #define __list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) /** * list_for_each_prev - iterate over a list backwards * @pos: the &struct list_head to use as a loop counter. * @head: the head for your list. */ #define list_for_each_prev(pos, head) \ for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \ pos = pos->prev) /** * list_for_each_safe - iterate over a list safe against removal of list entry * @pos: the &struct list_head to use as a loop counter. * @n: another &struct list_head to use as temporary storage * @head: the head for your list. */ #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) /** * list_for_each_entry - iterate over list of given type * @pos: the type * to use as a loop counter. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #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)) /** * list_for_each_entry_reverse - iterate backwards over list of given type. * @pos: the type * to use as a loop counter. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_reverse(pos, head, member) \ for (pos = list_entry((head)->prev, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.prev, typeof(*pos), member)) /** * list_prepare_entry - prepare a pos entry for use as a start point in * list_for_each_entry_continue * @pos: the type * to use as a start point * @head: the head of the list * @member: the name of the list_struct within the struct. */ #define list_prepare_entry(pos, head, member) \ ((pos) ? : list_entry(head, typeof(*pos), member)) /** * list_for_each_entry_continue - iterate over list of given type * continuing after existing point * @pos: the type * to use as a loop counter. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_continue(pos, head, member) \ for (pos = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) /** * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry * @pos: the type * to use as a loop counter. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #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)) /** * list_for_each_entry_safe_continue - iterate over list of given type * continuing after existing point safe against removal of list entry * @pos: the type * to use as a loop counter. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_safe_continue(pos, n, head, member) \ for (pos = list_entry(pos->member.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)) /** * list_for_each_entry_safe_reverse - iterate backwards over list of given type safe against * removal of list entry * @pos: the type * to use as a loop counter. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_safe_reverse(pos, n, head, member) \ for (pos = list_entry((head)->prev, typeof(*pos), member), \ n = list_entry(pos->member.prev, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.prev, typeof(*n), member)) /* * Double linked lists with a single pointer list head. * Mostly useful for hash tables where the two pointer list head is * too wasteful. * You lose the ability to access the tail in O(1). */ struct hlist_head { struct hlist_node *first; }; struct hlist_node { struct hlist_node *next, **pprev; }; #define HLIST_HEAD_INIT { .first = NULL } #define HLIST_HEAD(name) struct hlist_head name = { .first = NULL } #define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) #define INIT_HLIST_NODE(ptr) ((ptr)->next = NULL, (ptr)->pprev = NULL) static inline int hlist_unhashed(const struct hlist_node *h) { return !h->pprev; } static inline int hlist_empty(const struct hlist_head *h) { return !h->first; } static inline void __hlist_del(struct hlist_node *n) { struct hlist_node *next = n->next; struct hlist_node **pprev = n->pprev; *pprev = next; if (next) next->pprev = pprev; } static inline void hlist_del(struct hlist_node *n) { __hlist_del(n); n->next = LIST_POISON1; n->pprev = LIST_POISON2; } static inline void hlist_del_init(struct hlist_node *n) { if (n->pprev) { __hlist_del(n); INIT_HLIST_NODE(n); } } static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) { struct hlist_node *first = h->first; n->next = first; if (first) first->pprev = &n->next; h->first = n; n->pprev = &h->first; } /* next must be != NULL */ static inline void hlist_add_before(struct hlist_node *n, struct hlist_node *next) { n->pprev = next->pprev; n->next = next; next->pprev = &n->next; *(n->pprev) = n; } static inline void hlist_add_after(struct hlist_node *n, struct hlist_node *next) { next->next = n->next; n->next = next; next->pprev = &n->next; if(next->next) next->next->pprev = &next->next; } #define hlist_entry(ptr, type, member) container_of(ptr,type,member) #define hlist_for_each(pos, head) \ for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \ pos = pos->next) #define hlist_for_each_safe(pos, n, head) \ for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \ pos = n) /** * hlist_for_each_entry - iterate over list of given type * @tpos: the type * to use as a loop counter. * @pos: the &struct hlist_node to use as a loop counter. * @head: the head for your list. * @member: the name of the hlist_node within the struct. */ #define hlist_for_each_entry(tpos, pos, head, member) \ for (pos = (head)->first; \ pos && ({ prefetch(pos->next); 1;}) && \ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ pos = pos->next) /** * hlist_for_each_entry_continue - iterate over a hlist continuing after existing point * @tpos: the type * to use as a loop counter. * @pos: the &struct hlist_node to use as a loop counter. * @member: the name of the hlist_node within the struct. */ #define hlist_for_each_entry_continue(tpos, pos, member) \ for (pos = (pos)->next; \ pos && ({ prefetch(pos->next); 1;}) && \ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ pos = pos->next) /** * hlist_for_each_entry_from - iterate over a hlist continuing from existing point * @tpos: the type * to use as a loop counter. * @pos: the &struct hlist_node to use as a loop counter. * @member: the name of the hlist_node within the struct. */ #define hlist_for_each_entry_from(tpos, pos, member) \ for (; pos && ({ prefetch(pos->next); 1;}) && \ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ pos = pos->next) /** * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry * @tpos: the type * to use as a loop counter. * @pos: the &struct hlist_node to use as a loop counter. * @n: another &struct hlist_node to use as temporary storage * @head: the head for your list. * @member: the name of the hlist_node within the struct. */ #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) #endif mstpd-mstpd-8995e5d/log.h000066400000000000000000000062501516242366700153150ustar00rootroot00000000000000/***************************************************************************** Copyright (c) 2006 EMC Corporation. Copyright (c) 2011 Factor-SPE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. Authors: Srinivas Aji Authors: Vitalii Demianets ******************************************************************************/ #ifndef LOG_H #define LOG_H #include #include #define LOG_LEVEL_NONE 0 #define LOG_LEVEL_ERROR 1 #define LOG_LEVEL_INFO 2 #define LOG_LEVEL_DEBUG 3 #define LOG_LEVEL_STATE_MACHINE_TRANSITION 4 #define LOG_LEVEL_MAX 100 #define LOG_LEVEL_DEFAULT LOG_LEVEL_INFO extern void Dprintf(int level, const char *fmt, ...); extern void vDprintf(int level, const char *fmt, va_list ap); extern int log_level; #define PRINT(_level, _fmt, _args...) Dprintf(_level, _fmt, ##_args) #define TSTM(x, y, _fmt, _args...) \ do if(!(x)) \ { \ PRINT(LOG_LEVEL_ERROR, "Error in %s at %s:%d verifying %s. " _fmt, \ __PRETTY_FUNCTION__, __FILE__, __LINE__, #x, ##_args); \ return y; \ } while (0) #define TST(x, y) TSTM(x, y, "") #define LOG(_fmt, _args...) \ PRINT(LOG_LEVEL_DEBUG, "%s: " _fmt, __PRETTY_FUNCTION__, ##_args) #define INFO(_fmt, _args...) \ PRINT(LOG_LEVEL_INFO, "%s: " _fmt, __PRETTY_FUNCTION__, ##_args) #ifdef NO_DAEMON #define ERROR(_fmt, _args...) \ PRINT(LOG_LEVEL_ERROR, "%s: " _fmt, __PRETTY_FUNCTION__, ##_args) #else #include "ctl_socket_server.h" #define ERROR(_fmt, _args...) \ ({ \ PRINT(LOG_LEVEL_ERROR, "error, %s: " _fmt, __PRETTY_FUNCTION__, \ ##_args); \ ctl_err_log(_fmt "\n", ##_args); \ }) #endif static inline void dump_hex(void *b, int l) { unsigned char *buf = b; char logbuf[80]; int i, j; for (i = 0; i < l; i += 16) { for (j = 0; j < 16 && i + j < l; ++j) sprintf(logbuf + j * 3, " %02x", buf[i + j]); PRINT(LOG_LEVEL_INFO, "%s", logbuf); } PRINT(LOG_LEVEL_INFO, "\n"); } #endif /* LOG_H */ mstpd-mstpd-8995e5d/m4/000077500000000000000000000000001516242366700147005ustar00rootroot00000000000000mstpd-mstpd-8995e5d/m4/.place_holder000066400000000000000000000000001516242366700173100ustar00rootroot00000000000000mstpd-mstpd-8995e5d/main.c000066400000000000000000000212011516242366700154440ustar00rootroot00000000000000/***************************************************************************** Copyright (c) 2006 EMC Corporation. Copyright (c) 2011 Factor-SPE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. Authors: Srinivas Aji Authors: Vitalii Demianets ******************************************************************************/ /* #define MISC_TEST_FUNCS */ #include #include #include #include #include #include #include #include "epoll_loop.h" #include "bridge_ctl.h" #include "netif_utils.h" #include "packet.h" #include "log.h" #include "mstp.h" #include "ctl_socket_server.h" #include "driver.h" #include "bridge_track.h" #define APP_NAME "mstpd" static int print_to_syslog = 0; int log_level = LOG_LEVEL_DEFAULT; #ifdef MISC_TEST_FUNCS static bool test_ports_trees_mesh(void); #endif /* MISC_TEST_FUNCS */ volatile bool quit = false; static void handle_signal(int sig) { quit = true; } static void handle_sigusr1(int sig) { log_level = LOG_LEVEL_DEBUG; } int signal_init(void) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = handle_signal; sa.sa_flags = 0; sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGHUP, &sa, NULL); sigaction(SIGUSR2, &sa, NULL); sa.sa_handler = handle_sigusr1; sigaction(SIGUSR1, &sa, NULL); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, NULL); return 0; } static int sanity_check(void) { bridge_identifier_t BridgeIdentifier; mst_configuration_identifier_t MST_ConfigurationIdentifier; TST(sizeof(BridgeIdentifier) == 8, -1); TST(sizeof(BridgeIdentifier.u) == 8, -1); TST(sizeof(BridgeIdentifier.s) == 8, -1); TST(sizeof(BridgeIdentifier.s.priority) == 2, -1); TST(sizeof(BridgeIdentifier.s.mac_address) == 6, -1); TST(sizeof(MST_ConfigurationIdentifier) == 51, -1); TST(sizeof(MST_ConfigurationIdentifier.a) == 51, -1); TST(sizeof(MST_ConfigurationIdentifier.s) == 51, -1); #ifdef HMAC_MDS_TEST_FUNCTIONS TST(MD5TestSuite(), -1); #endif /* HMAC_MDS_TEST_FUNCTIONS */ #ifdef MISC_TEST_FUNCS TST(test_ports_trees_mesh(), -1); #endif /* MISC_TEST_FUNCS */ INFO("Sanity checks succeeded"); return 0; } int main(int argc, char *argv[]) { int c; int daemonize = 1; while((c = getopt(argc, argv, "Vdsv:")) != -1) { switch (c) { case 'd': daemonize = 0; break; case 's': print_to_syslog = 1; break; case 'v': { char *end; long l; l = strtoul(optarg, &end, 0); if(*optarg == 0 || *end != 0 || l > LOG_LEVEL_MAX) { ERROR("Invalid loglevel %s", optarg); exit(1); } log_level = l; break; } case 'V': printf(PACKAGE_VERSION "\n"); return 0; default: return -1; } } if(daemonize) print_to_syslog = 1; if(print_to_syslog) openlog(APP_NAME, LOG_PID, LOG_DAEMON); INFO("Version - " PACKAGE_VERSION "\n"); if (sanity_check() < 0) return EXIT_FAILURE; if(daemonize) { FILE *f = fopen(MSTPD_PID_FILE, "w"); if(!f) { ERROR("can't open "MSTPD_PID_FILE); return -1; } if(daemon(0, 0)) { ERROR("can't daemonize"); fclose(f); return -1; } fprintf(f, "%d", getpid()); fclose(f); } TST(signal_init() == 0, -1); TST(driver_mstp_init() == 0, -1); TST(init_epoll() == 0, -1); TST(ctl_socket_init() == 0, -1); TST(packet_sock_init() == 0, -1); TST(netsock_init() == 0, -1); TST(init_bridge_ops() == 0, -1); c = epoll_main_loop(&quit); bridge_track_fini(); ctl_socket_cleanup(); driver_mstp_fini(); return c; } /*********************** Logging *********************/ #include #include void vDprintf(int level, const char *fmt, va_list ap) { if(level > log_level) return; if(!print_to_syslog) { char logbuf[256]; logbuf[255] = 0; time_t clock; struct tm *local_tm; time(&clock); local_tm = localtime(&clock); int l = strftime(logbuf, sizeof(logbuf) - 1, "%F %T ", local_tm); vsnprintf(logbuf + l, sizeof(logbuf) - l - 1, fmt, ap); printf("%s\n", logbuf); } else { vsyslog((level <= LOG_LEVEL_INFO) ? LOG_INFO : LOG_DEBUG, fmt, ap); } } void Dprintf(int level, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vDprintf(level, fmt, ap); va_end(ap); } /*********************** Testing *********************/ #ifdef MISC_TEST_FUNCS #include #include static void printout_mesh(bridge_t *br) { tree_t *tree; port_t *prt; per_tree_port_t *ptp; printf("Ports:\n"); list_for_each_entry(prt, &br->ports, br_list) { printf(" %s(%03hX)", prt->sysdeps.name, __be16_to_cpu(prt->port_number)); list_for_each_entry(ptp, &prt->trees, port_list) printf("->%03hX", __be16_to_cpu(ptp->MSTID)); printf("\n"); } printf("\nTrees:\n"); list_for_each_entry(tree, &br->trees, bridge_list) { printf(" %03hX", __be16_to_cpu(tree->MSTID)); list_for_each_entry(ptp, &tree->ports, tree_list) printf("->%s", ptp->port->sysdeps.name); printf("\n"); } printf("\n"); } static bool test_ports_trees_mesh(void) { bridge_t *br = calloc(1, sizeof(*br)); if(!br) return false; strcpy(br->sysdeps.name, "BR_TEST"); br->sysdeps.macaddr[5] = 0xED; if(!MSTP_IN_bridge_create(br, br->sysdeps.macaddr)) { free(br); return false; } port_t *prt[5]; int i; for(i = 0; i < 5; ++i) { if(!(prt[i] = calloc(1, sizeof(port_t)))) return false; prt[i]->bridge = br; } if(!MSTP_IN_create_msti(br, 0xF91)) { error_exit: MSTP_IN_delete_bridge(br); free(br); return false; } if(!MSTP_IN_create_msti(br, 0xE10)) goto error_exit; strcpy(prt[0]->sysdeps.name, "PRT_10C"); if(!MSTP_IN_port_create_and_add_tail(prt[0], 0x10C)) goto error_exit; strcpy(prt[1]->sysdeps.name, "PRT_001"); if(!MSTP_IN_port_create_and_add_tail(prt[1], 0x001)) goto error_exit; strcpy(prt[2]->sysdeps.name, "PRT_C01"); if(!MSTP_IN_port_create_and_add_tail(prt[2], 0xC01)) goto error_exit; if(!MSTP_IN_create_msti(br, 0xE12)) goto error_exit; if(!MSTP_IN_create_msti(br, 0x001)) goto error_exit; strcpy(prt[3]->sysdeps.name, "PRT_002"); if(!MSTP_IN_port_create_and_add_tail(prt[3], 0x002)) goto error_exit; strcpy(prt[4]->sysdeps.name, "PRT_003"); if(!MSTP_IN_port_create_and_add_tail(prt[4], 0x003)) goto error_exit; if(!MSTP_IN_create_msti(br, 0x005)) goto error_exit; printout_mesh(br); MSTP_IN_delete_port(prt[1]); if(!MSTP_IN_delete_msti(br, 0xE12)) goto error_exit; MSTP_IN_delete_port(prt[3]); if(!MSTP_IN_delete_msti(br, 0x005)) goto error_exit; printout_mesh(br); if(!MSTP_IN_create_msti(br, 0x102)) goto error_exit; strcpy(prt[1]->sysdeps.name, "PRT_504"); if(!MSTP_IN_port_create_and_add_tail(prt[1], 0x504)) goto error_exit; if(!MSTP_IN_create_msti(br, 0x105)) goto error_exit; strcpy(prt[3]->sysdeps.name, "PRT_777"); if(!MSTP_IN_port_create_and_add_tail(prt[3], 0x777)) goto error_exit; printout_mesh(br); MSTP_IN_delete_bridge(br); free(br); return true; } #endif /* MISC_TEST_FUNCS */ mstpd-mstpd-8995e5d/mstp.c000066400000000000000000004621441516242366700155220ustar00rootroot00000000000000/* * mstp.c State machines from IEEE 802.1Q-2005 * * This program is free software; you can redistribute it and/or * modify it under the terms of the 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: Vitalii Demianets */ /* NOTE: The standard messes up Hello_Time timer management. * The portTimes and msgTimes structures have it, while * designatedTimes, rootTimes and BridgeTimes do not! * And there are places, where standard says: * "portTimes = designatedTimes" (13.32) * "rootTimes = portTimes" (13.26.23) * ---- Bad IEEE! ---- * For now I decide: All structures will hold Hello_Time, * because in 802.1D they do. * Besides, it is necessary for compatibility with old STP implementations. */ /* 802.1Q-2005 does not define but widely use variable name newInfoXst. * From the 802.1s I can guess that it means: * - "newInfo" when tree is CIST; * - "newInfoMsti" when tree is not CIST (is MSTI). * But that is only a guess and I could be wrong here ;) */ /* Important coding convention: * All functions that have dry_run argument must follow the * return value convention: * They should return true if state change detected during dry run. * Otherwise (!dry_run || !state_change) they return false. */ #include #include #include #include #include #include #include "mstp.h" #include "log.h" #include "driver.h" #include "clock_gettime.h" static void PTSM_tick(port_t *prt); static bool TCSM_run(per_tree_port_t *ptp, bool dry_run); static void BDSM_begin(port_t *prt); static void br_state_machines_begin(bridge_t *br); static void prt_state_machines_begin(port_t *prt); static void tree_state_machines_begin(tree_t *tree); static void br_state_machines_run(bridge_t *br); static void updtbrAssuRcvdInfoWhile(port_t *prt); #define FOREACH_PORT_IN_BRIDGE(port, bridge) \ list_for_each_entry((port), &(bridge)->ports, br_list) #define FOREACH_TREE_IN_BRIDGE(tree, bridge) \ list_for_each_entry((tree), &(bridge)->trees, bridge_list) #define FOREACH_PTP_IN_TREE(ptp, tree) \ list_for_each_entry((ptp), &(tree)->ports, tree_list) #define FOREACH_PTP_IN_PORT(ptp, port) \ list_for_each_entry((ptp), &(port)->trees, port_list) /* 17.20.11 of 802.1D */ #define rstpVersion(br) ((br)->ForceProtocolVersion >= protoRSTP) /* Bridge assurance is operational only when NetworkPort type is configured * and the operation status is pointToPoint and version is RSTP/MSTP */ #define assurancePort(prt) ((prt)->NetworkPort && (prt)->operPointToPointMAC \ && (prt)->sendRSTP) /* * Recalculate configuration digest. (13.7) */ static void RecalcConfigDigest(bridge_t *br) { __be16 vid2mstid[MAX_VID + 2]; unsigned char mstp_key[] = HMAC_KEY; int vid; vid2mstid[0] = vid2mstid[MAX_VID + 1] = 0; for(vid = 1; vid <= MAX_VID; ++vid) vid2mstid[vid] = br->fid2mstid[br->vid2fid[vid]]; hmac_md5((void *)vid2mstid, sizeof(vid2mstid), mstp_key, sizeof(mstp_key), (caddr_t)br->MstConfigId.s.configuration_digest); } /* * 13.37.1 - Table 13-3 */ static __u32 compute_pcost(int speed) { /* speed is in MB/s*/ if(speed > 0) return (speed < 20000000) ? 20000000 / speed : 1; else return MAX_PATH_COST; } static void bridge_default_internal_vars(bridge_t *br) { br->uptime = 0; } static void tree_default_internal_vars(tree_t *tree) { /* 12.8.1.1.3.(b,c,d) */ tree->time_since_topology_change = 0; tree->topology_change_count = 0; tree->topology_change = false; /* since all tcWhile are initialized to 0 */ strncpy(tree->topology_change_port, "None", IFNAMSIZ); strncpy(tree->last_topology_change_port, "None", IFNAMSIZ); /* The following are initialized in BEGIN state: * - rootPortId, rootPriority, rootTimes: in Port Role Selection SM */ } static void port_default_internal_vars(port_t *prt) { prt->infoInternal = false; prt->rcvdInternal = false; prt->rcvdTcAck = false; prt->rcvdTcn = false; assign(prt->rapidAgeingWhile, 0u); assign(prt->brAssuRcvdInfoWhile, 0u); prt->BaInconsistent = false; prt->num_rx_bpdu_filtered = 0; prt->num_rx_bpdu = 0; prt->num_rx_tcn = 0; prt->num_tx_bpdu = 0; prt->num_tx_tcn = 0; prt->num_trans_fwd = 0; prt->num_trans_blk = 0; /* The following are initialized in BEGIN state: * - mdelayWhile. mcheck, sendRSTP: in Port Protocol Migration SM * - helloWhen, newInfo, newInfoMsti, txCount: in Port Transmit SM * - edgeDelayWhile, rcvdBpdu, rcvdRSTP, rcvdSTP : in Port Receive SM * - operEdge: in Bridge Detection SM * - tcAck: in Topology Change SM */ } static void ptp_default_internal_vars(per_tree_port_t *ptp) { ptp->rcvdTc = false; ptp->tcProp = false; ptp->updtInfo = false; ptp->master = false; /* 13.24.5 */ ptp->disputed = false; assign(ptp->rcvdInfo, (port_info_t)0); ptp->mastered = false; memset(&ptp->msgPriority, 0, sizeof(ptp->msgPriority)); memset(&ptp->msgTimes, 0, sizeof(ptp->msgTimes)); /* The following are initialized in BEGIN state: * - rcvdMsg: in Port Receive SM * - fdWhile, rrWhile, rbWhile, role, learn, forward, * sync, synced, reRoot: in Port Role Transitions SM * - tcWhile, fdbFlush: Topology Change SM * - rcvdInfoWhile, proposed, proposing, agree, agreed, * infoIs, reselect, selected: Port Information SM * - forwarding, learning: Port State Transition SM * - selectedRole, designatedPriority, designatedTimes: Port Role Selection SM */ } static tree_t * create_tree(bridge_t *br, __u8 *macaddr, __be16 MSTID) { /* Initialize all fields except anchor */ tree_t *tree = calloc(1, sizeof(*tree)); if(!tree) { ERROR_BRNAME(br, "Out of memory"); return NULL; } tree->bridge = br; tree->MSTID = MSTID; INIT_LIST_HEAD(&tree->ports); memcpy(tree->BridgeIdentifier.s.mac_address, macaddr, ETH_ALEN); /* 0x8000 = default bridge priority (17.14 of 802.1D) */ tree->BridgeIdentifier.s.priority = __constant_cpu_to_be16(0x8000) | MSTID; assign(tree->BridgePriority.RootID, tree->BridgeIdentifier); assign(tree->BridgePriority.RRootID, tree->BridgeIdentifier); assign(tree->BridgePriority.DesignatedBridgeID, tree->BridgeIdentifier); /* 13.23.4 */ assign(tree->BridgeTimes.remainingHops, br->MaxHops); assign(tree->BridgeTimes.Forward_Delay, br->Forward_Delay); assign(tree->BridgeTimes.Max_Age, br->Max_Age); assign(tree->BridgeTimes.Message_Age, (__u8)0); assign(tree->BridgeTimes.Hello_Time, br->Hello_Time); tree_default_internal_vars(tree); return tree; } static per_tree_port_t * create_ptp(tree_t *tree, port_t *prt) { /* Initialize all fields except anchors */ per_tree_port_t *ptp = calloc(1, sizeof(*ptp)); if(!ptp) { ERROR_PRTNAME(prt->bridge, prt, "Out of memory"); return NULL; } ptp->port = prt; ptp->tree = tree; ptp->MSTID = tree->MSTID; ptp->state = BR_STATE_DISABLED; /* 0x80 = default port priority (17.14 of 802.1D) */ ptp->portId = __constant_cpu_to_be16(0x8000) | prt->port_number; assign(ptp->AdminInternalPortPathCost, 0u); assign(ptp->InternalPortPathCost, compute_pcost(GET_PORT_SPEED(prt))); /* 802.1Q leaves portPriority and portTimes uninitialized */ assign(ptp->portPriority, tree->BridgePriority); assign(ptp->portTimes, tree->BridgeTimes); ptp->calledFromFlushRoutine = false; ptp_default_internal_vars(ptp); return ptp; } /* External events */ bool MSTP_IN_bridge_create(bridge_t *br, __u8 *macaddr) { tree_t *cist; if (!driver_create_bridge(br, macaddr)) return false; /* Initialize all fields except sysdeps and anchor */ INIT_LIST_HEAD(&br->ports); INIT_LIST_HEAD(&br->trees); br->bridgeEnabled = false; memset(br->vid2fid, 0, sizeof(br->vid2fid)); memset(br->fid2mstid, 0, sizeof(br->fid2mstid)); assign(br->MstConfigId.s.selector, (__u8)0); sprintf((char *)br->MstConfigId.s.configuration_name, "%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX", macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5]); assign(br->MstConfigId.s.revision_level, __constant_cpu_to_be16(0)); RecalcConfigDigest(br); /* set br->MstConfigId.s.configuration_digest */ br->ForceProtocolVersion = protoRSTP; assign(br->MaxHops, (__u8)20); /* 13.37.3 */ assign(br->Forward_Delay, (__u8)15); /* 17.14 of 802.1D */ assign(br->Max_Age, (__u8)20); /* 17.14 of 802.1D */ assign(br->Transmit_Hold_Count, 6u); /* 17.14 of 802.1D */ assign(br->Migrate_Time, 3u); /* 17.14 of 802.1D */ assign(br->Ageing_Time, 300u);/* 8.8.3 Table 8-3 */ assign(br->Hello_Time, (__u8)2); /* 17.14 of 802.1D */ bridge_default_internal_vars(br); /* Create CIST */ if(!(cist = create_tree(br, macaddr, 0))) return false; list_add_tail(&cist->bridge_list, &br->trees); return true; } bool MSTP_IN_port_create_and_add_tail(port_t *prt, __u16 portno) { tree_t *tree; per_tree_port_t *ptp, *nxt; bridge_t *br = prt->bridge; if (!driver_create_port(prt, portno)) return false; /* Initialize all fields except sysdeps and bridge */ INIT_LIST_HEAD(&prt->trees); prt->port_number = __cpu_to_be16(portno); assign(prt->AdminExternalPortPathCost, 0u); /* Default for operP2P is false because by default AdminP2P * says to auto-detect p2p state, and it is derived from duplex * and initially port is in down state and in this down state * duplex is set to false (half) */ prt->AdminP2P = p2pAuto; prt->operPointToPointMAC = false; prt->portEnabled = false; prt->restrictedRole = false; /* 13.25.14 */ prt->restrictedTcn = false; /* 13.25.15 */ assign(prt->ExternalPortPathCost, MAX_PATH_COST); /* 13.37.1 */ prt->AdminEdgePort = false; /* 13.25 */ prt->AutoEdge = true; /* 13.25 */ prt->BpduGuardPort = false; prt->BpduGuardError = false; prt->NetworkPort = false; prt->dontTxmtBpdu = false; prt->bpduFilterPort = false; prt->deleted = false; port_default_internal_vars(prt); /* Create PerTreePort structures for all existing trees */ FOREACH_TREE_IN_BRIDGE(tree, br) { if(!(ptp = create_ptp(tree, prt))) { /* Remove and free all previously created entries in port's list */ list_for_each_entry_safe(ptp, nxt, &prt->trees, port_list) { list_del(&ptp->port_list); list_del(&ptp->tree_list); free(ptp); } return false; } list_add_tail(&ptp->port_list, &prt->trees); list_add_tail(&ptp->tree_list, &tree->ports); } /* Add new port to the tail of the list in the bridge */ /* NOTE: if one wants add port NOT to the tail of the list of ports, * one should revise above loop (FOREACH_TREE_IN_BRIDGE) * because it heavily depends on the fact that port is added to the tail. */ list_add_tail(&prt->br_list, &br->ports); prt_state_machines_begin(prt); return true; } void MSTP_IN_delete_port(port_t *prt) { per_tree_port_t *ptp, *nxt; bridge_t *br = prt->bridge; driver_delete_port(prt); prt->deleted = true; if(prt->portEnabled) { prt->portEnabled = false; br_state_machines_run(br); } list_for_each_entry_safe(ptp, nxt, &prt->trees, port_list) { list_del(&ptp->port_list); list_del(&ptp->tree_list); free(ptp); } list_del(&prt->br_list); br_state_machines_run(br); } void MSTP_IN_delete_bridge(bridge_t *br) { tree_t *tree, *nxt_tree; port_t *prt, *nxt_prt; driver_delete_bridge(br); br->bridgeEnabled = false; /* We SHOULD first delete all ports and only THEN delete all tree_t * structures as the tree_t structure contains the head for the per-port * list of tree data (tree_t.ports). * If this list_head will be deleted before all the per_tree_ports * bad things will happen ;) */ list_for_each_entry_safe(prt, nxt_prt, &br->ports, br_list) { MSTP_IN_delete_port(prt); free(prt); } list_for_each_entry_safe(tree, nxt_tree, &br->trees, bridge_list) { list_del(&tree->bridge_list); free(tree); } } void MSTP_IN_set_bridge_address(bridge_t *br, __u8 *macaddr) { tree_t *tree; bool changed = false; FOREACH_TREE_IN_BRIDGE(tree, br) { if(0 == memcmp(tree->BridgeIdentifier.s.mac_address, macaddr, ETH_ALEN)) continue; changed = true; memcpy(tree->BridgeIdentifier.s.mac_address, macaddr, ETH_ALEN); tree->BridgePriority.RootID = tree->BridgePriority.RRootID = tree->BridgePriority.DesignatedBridgeID = tree->BridgeIdentifier; } if(changed) br_state_machines_begin(br); } void MSTP_IN_set_bridge_enable(bridge_t *br, bool up) { port_t *prt; per_tree_port_t *ptp; tree_t *tree; if(br->bridgeEnabled == up) return; br->bridgeEnabled = up; /* Reset all internal states and variables, * except those which are user-configurable */ bridge_default_internal_vars(br); FOREACH_TREE_IN_BRIDGE(tree, br) { tree_default_internal_vars(tree); } FOREACH_PORT_IN_BRIDGE(prt, br) { /* NOTE: Don't check prt->deleted here, as it is imposible condition */ /* NOTE: In the port_default_internal_vars() rapidAgeingWhile will be * reset, so we should stop rapid ageing procedure here. */ if(prt->rapidAgeingWhile) { MSTP_OUT_set_ageing_time(prt, br->Ageing_Time); } port_default_internal_vars(prt); FOREACH_PTP_IN_PORT(ptp, prt) { if(BR_STATE_DISABLED != ptp->state) { MSTP_OUT_set_state(ptp, BR_STATE_DISABLED); } ptp_default_internal_vars(ptp); } } br_state_machines_begin(br); } void MSTP_IN_set_port_enable(port_t *prt, bool up, int speed, int duplex) { __u32 computed_pcost, new_ExternalPathCost, new_InternalPathCost; per_tree_port_t *ptp; bool new_p2p; bool changed = false; if(up) { computed_pcost = compute_pcost(speed); new_ExternalPathCost = (0 == prt->AdminExternalPortPathCost) ? computed_pcost : prt->AdminExternalPortPathCost; if(prt->ExternalPortPathCost != new_ExternalPathCost) { assign(prt->ExternalPortPathCost, new_ExternalPathCost); changed = true; } FOREACH_PTP_IN_PORT(ptp, prt) { new_InternalPathCost = (0 == ptp->AdminInternalPortPathCost) ? computed_pcost : ptp->AdminInternalPortPathCost; if(ptp->InternalPortPathCost != new_InternalPathCost) { assign(ptp->InternalPortPathCost, new_InternalPathCost); changed = true; } } switch(prt->AdminP2P) { case p2pForceTrue: new_p2p = true; break; case p2pForceFalse: new_p2p = false; break; case p2pAuto: default: new_p2p = !!duplex; break; } if(prt->operPointToPointMAC != new_p2p) { prt->operPointToPointMAC = new_p2p; changed = true; } if(!prt->portEnabled) { prt->portEnabled = true; prt->BpduGuardError = false; prt->BaInconsistent = false; prt->num_rx_bpdu_filtered = 0; prt->num_rx_bpdu = 0; prt->num_rx_tcn = 0; prt->num_tx_bpdu = 0; prt->num_tx_tcn = 0; changed = true; /* When port is enabled, initialize bridge assurance timer, * so that enough time is given before port is put in * inconsistent state. */ updtbrAssuRcvdInfoWhile(prt); } } else { if(prt->portEnabled) { prt->portEnabled = false; changed = true; } } if(changed) br_state_machines_run(prt->bridge); } void MSTP_IN_one_second(bridge_t *br) { port_t *prt; tree_t *tree; ++(br->uptime); if(!br->bridgeEnabled) return; FOREACH_TREE_IN_BRIDGE(tree, br) if(!(tree->topology_change)) ++(tree->time_since_topology_change); FOREACH_PORT_IN_BRIDGE(prt, br) { PTSM_tick(prt); /* support for rapid ageing */ if(prt->rapidAgeingWhile) { if((--(prt->rapidAgeingWhile)) == 0) { if(!prt->deleted) MSTP_OUT_set_ageing_time(prt, br->Ageing_Time); } } } br_state_machines_run(br); } void MSTP_IN_all_fids_flushed(per_tree_port_t *ptp) { bridge_t *br = ptp->port->bridge; ptp->fdbFlush = false; if(!br->bridgeEnabled) return; if(!ptp->calledFromFlushRoutine) { TCSM_run(ptp, false /* actual run */); br_state_machines_run(br); } } /* NOTE: bpdu pointer is unaligned, but it works because * bpdu_t is packed. Don't try to cast bpdu to non-packed type ;) */ void MSTP_IN_rx_bpdu(port_t *prt, bpdu_t *bpdu, int size) { int mstis_size; bridge_t *br = prt->bridge; ++(prt->num_rx_bpdu); if(prt->BpduGuardPort) { prt->BpduGuardError = true; ERROR_PRTNAME(br, prt, "Received BPDU on BPDU Guarded Port - Port Down"); MSTP_OUT_shutdown_port(prt); return; } if(prt->bpduFilterPort) { LOG_PRTNAME(br, prt, "Received BPDU on BPDU Filtered Port - discarded"); ++(prt->num_rx_bpdu_filtered); return; } if(!br->bridgeEnabled) { INFO_PRTNAME(br, prt, "Received BPDU while bridge is disabled"); return; } if(prt->rcvdBpdu) { ERROR_PRTNAME(br, prt, "Port hasn't processed previous BPDU"); return; } /* 14.4 Validation */ if((TCN_BPDU_SIZE > size) || (0 != bpdu->protocolIdentifier)) { bpdu_validation_failed: INFO_PRTNAME(br, prt, "BPDU validation failed"); return; } switch(bpdu->bpduType) { case bpduTypeTCN: /* 14.4.b) */ /* Valid TCN BPDU */ bpdu->protocolVersion = protoSTP; LOG_PRTNAME(br, prt, "received TCN BPDU"); break; case bpduTypeConfig: /* 14.4.a) */ if(CONFIG_BPDU_SIZE > size) goto bpdu_validation_failed; /* Valid Config BPDU */ bpdu->protocolVersion = protoSTP; LOG_PRTNAME(br, prt, "received Config BPDU%s", (bpdu->flags & (1 << offsetTc)) ? ", tcFlag" : "" ); break; case bpduTypeRST: if(protoRSTP == bpdu->protocolVersion) { /* 14.4.c) */ if(RST_BPDU_SIZE > size) goto bpdu_validation_failed; /* Valid RST BPDU */ /* bpdu->protocolVersion = protoRSTP; */ LOG_PRTNAME(br, prt, "received RST BPDU%s", (bpdu->flags & (1 << offsetTc)) ? ", tcFlag" : "" ); break; } if(protoMSTP > bpdu->protocolVersion) goto bpdu_validation_failed; /* Yes, 802.1Q-2005 says here to check if it contains * "35 or more octets", not 36! (see 14.4.d).1) ) * That's why I check size against CONFIG_BPDU_SIZE * and not RST_BPDU_SIZE. */ if(CONFIG_BPDU_SIZE > size) goto bpdu_validation_failed; mstis_size = __be16_to_cpu(bpdu->version3_len) - MST_BPDU_VER3LEN_WO_MSTI_MSGS; if((MST_BPDU_SIZE_WO_MSTI_MSGS > size) || (0 != bpdu->version1_len) || (0 > mstis_size) || ((MAX_STANDARD_MSTIS * sizeof(msti_configuration_message_t)) < mstis_size) || (0 != (mstis_size % sizeof(msti_configuration_message_t))) ) { /* 14.4.d) */ /* Valid RST BPDU */ bpdu->protocolVersion = protoRSTP; LOG_PRTNAME(br, prt, "received RST BPDU"); break; } /* 14.4.e) */ /* Valid MST BPDU */ bpdu->protocolVersion = protoMSTP; prt->rcvdBpduNumOfMstis = mstis_size / sizeof(msti_configuration_message_t); LOG_PRTNAME(br, prt, "received MST BPDU%s with %d MSTIs", (bpdu->flags & (1 << offsetTc)) ? ", tcFlag" : "", prt->rcvdBpduNumOfMstis ); break; default: goto bpdu_validation_failed; } if((protoSTP == bpdu->protocolVersion) && (bpduTypeTCN == bpdu->bpduType)) { ++(prt->num_rx_tcn); } else { if(bpdu->flags & (1 << offsetTc)) ++(prt->num_rx_tcn); } assign(prt->rcvdBpduData, *bpdu); prt->rcvdBpdu = true; /* Reset bridge assurance on receipt of valid BPDU */ if(prt->BaInconsistent) { prt->BaInconsistent = false; INFO_PRTNAME(br, prt, "Clear Bridge assurance inconsistency"); } updtbrAssuRcvdInfoWhile(prt); br_state_machines_run(br); } /* 12.8.1.1 Read CIST Bridge Protocol Parameters */ void MSTP_IN_get_cist_bridge_status(bridge_t *br, CIST_BridgeStatus *status) { tree_t *cist = GET_CIST_TREE(br); assign(status->bridge_id, cist->BridgeIdentifier); assign(status->time_since_topology_change, cist->time_since_topology_change); assign(status->topology_change_count, cist->topology_change_count); status->topology_change = cist->topology_change; strncpy(status->topology_change_port, cist->topology_change_port, IFNAMSIZ); strncpy(status->last_topology_change_port, cist->last_topology_change_port, IFNAMSIZ); assign(status->designated_root, cist->rootPriority.RootID); assign(status->root_path_cost, __be32_to_cpu(cist->rootPriority.ExtRootPathCost)); assign(status->regional_root, cist->rootPriority.RRootID); assign(status->internal_path_cost, __be32_to_cpu(cist->rootPriority.IntRootPathCost)); assign(status->root_port_id, cist->rootPortId); assign(status->root_max_age, cist->rootTimes.Max_Age); assign(status->root_forward_delay, cist->rootTimes.Forward_Delay); assign(status->bridge_max_age, br->Max_Age); assign(status->bridge_forward_delay, br->Forward_Delay); assign(status->max_hops, br->MaxHops); assign(status->tx_hold_count, br->Transmit_Hold_Count); status->protocol_version = br->ForceProtocolVersion; status->enabled = br->bridgeEnabled; assign(status->bridge_hello_time, br->Hello_Time); assign(status->Ageing_Time, br->Ageing_Time); } /* 12.8.1.2 Read MSTI Bridge Protocol Parameters */ void MSTP_IN_get_msti_bridge_status(tree_t *tree, MSTI_BridgeStatus *status) { assign(status->bridge_id, tree->BridgeIdentifier); assign(status->time_since_topology_change, tree->time_since_topology_change); assign(status->topology_change_count, tree->topology_change_count); status->topology_change = tree->topology_change; strncpy(status->topology_change_port, tree->topology_change_port, IFNAMSIZ); strncpy(status->last_topology_change_port, tree->last_topology_change_port, IFNAMSIZ); assign(status->regional_root, tree->rootPriority.RRootID); assign(status->internal_path_cost, __be32_to_cpu(tree->rootPriority.IntRootPathCost)); assign(status->root_port_id, tree->rootPortId); } /* 12.8.1.3 Set CIST Bridge Protocol Parameters */ int MSTP_IN_set_cist_bridge_config(bridge_t *br, CIST_BridgeConfig *cfg) { bool changed, changedBridgeTimes, init; int r = 0; __u8 new_forward_delay, new_max_age; tree_t *tree; port_t *prt; per_tree_port_t *ptp; /* Firstly, validation */ if(cfg->set_bridge_max_age) { new_max_age = cfg->bridge_max_age; if((6 > new_max_age) || (40 < new_max_age)) { ERROR_BRNAME(br, "Bridge Max Age must be between 6 and 40 seconds"); r = -1; } } else new_max_age = br->Max_Age; if(cfg->set_bridge_forward_delay) { new_forward_delay = cfg->bridge_forward_delay; if((4 > new_forward_delay) || (30 < new_forward_delay)) { ERROR_BRNAME(br, "Bridge Forward Delay must be between 4 and 30 seconds"); r = -1; } } else new_forward_delay = br->Forward_Delay; if(cfg->set_bridge_max_age || cfg->set_bridge_forward_delay) { if((2 * (new_forward_delay - 1)) < new_max_age) { ERROR_BRNAME(br, "Configured Bridge Times don't meet " "2 * (Bridge Foward Delay - 1 second) >= Bridge Max Age"); r = -1; } } if(cfg->set_protocol_version) { switch(cfg->protocol_version) { case protoSTP: case protoRSTP: case protoMSTP: break; default: ERROR_BRNAME(br, "Bad protocol version (%d)", cfg->protocol_version); r = -1; } } if(cfg->set_tx_hold_count) { if((1 > cfg->tx_hold_count) || (10 < cfg->tx_hold_count)) { ERROR_BRNAME(br, "Transmit Hold Count must be between 1 and 10 seconds"); r = -1; } } if(cfg->set_max_hops) { if((6 > cfg->max_hops) || (100 < cfg->max_hops)) { ERROR_BRNAME(br, "Bridge Max Hops must be between 6 and 100"); r = -1; } } if(cfg->set_bridge_hello_time) { if((2 != cfg->bridge_hello_time)) { ERROR_BRNAME(br, "Bridge Hello Time must be 2"); r = -1; } } if(cfg->set_bridge_ageing_time) { if((10 > cfg->bridge_ageing_time)||(1000000 < cfg->bridge_ageing_time)) { ERROR_BRNAME(br, "Bridge Ageing Time must be between 10 and 1000000 seconds"); r = -1; } } if(r) return r; /* Secondly, do set */ changed = changedBridgeTimes = init = false; if(cfg->set_bridge_max_age || cfg->set_bridge_forward_delay) { if(cmp(new_max_age, !=, br->Max_Age) || cmp(new_forward_delay, !=, br->Forward_Delay) ) { assign(br->Max_Age, new_max_age); assign(br->Forward_Delay, new_forward_delay); changed = changedBridgeTimes = true; } } if((cfg->set_protocol_version) && (cfg->protocol_version != br->ForceProtocolVersion) ) { br->ForceProtocolVersion = cfg->protocol_version; changed = init = true; } if(cfg->set_tx_hold_count) { if(cfg->tx_hold_count != br->Transmit_Hold_Count) { assign(br->Transmit_Hold_Count, cfg->tx_hold_count); FOREACH_PORT_IN_BRIDGE(prt, br) assign(prt->txCount, 0u); changed = true; } } if(cfg->set_max_hops) { if(cfg->max_hops != br->MaxHops) { assign(br->MaxHops, cfg->max_hops); changed = changedBridgeTimes = true; } } if(cfg->set_bridge_ageing_time) { if(cfg->bridge_ageing_time != br->Ageing_Time) { INFO_BRNAME(br, "bridge ageing_time new=%u, old=%u", cfg->bridge_ageing_time, br->Ageing_Time); assign(br->Ageing_Time, cfg->bridge_ageing_time); } } /* Thirdly, finalize changes */ if(changedBridgeTimes) { FOREACH_TREE_IN_BRIDGE(tree, br) { assign(tree->BridgeTimes.remainingHops, br->MaxHops); assign(tree->BridgeTimes.Forward_Delay, br->Forward_Delay); assign(tree->BridgeTimes.Max_Age, br->Max_Age); assign(tree->BridgeTimes.Hello_Time, br->Hello_Time); /* Comment found in rstpd by Srinivas Aji: * Do this for any change in BridgeTimes. * Otherwise we fail UNH rstp.op_D test 3.2 since when administratively * setting BridgeForwardDelay, etc, the values don't propagate from * rootTimes to designatedTimes immediately without this change. */ FOREACH_PTP_IN_TREE(ptp, tree) { ptp->selected = false; ptp->reselect = true; /* TODO: change this when Hello_Time will be configurable * per-port. For now, copy Bridge's Hello_Time * to the port's Hello_Time. */ assign(ptp->portTimes.Hello_Time, br->Hello_Time); } } } if(changed && br->bridgeEnabled) { if(init) br_state_machines_begin(br); else br_state_machines_run(br); } return 0; } /* 12.8.1.4 Set MSTI Bridge Protocol Parameters */ int MSTP_IN_set_msti_bridge_config(tree_t *tree, __u8 bridge_priority) { per_tree_port_t *ptp; __u8 valuePri; if(15 < bridge_priority) { ERROR_BRNAME(tree->bridge, "MSTI %hu: Bridge Priority must be between 0 and 15", __be16_to_cpu(tree->MSTID)); return -1; } valuePri = bridge_priority << 4; if(GET_PRIORITY_FROM_IDENTIFIER(tree->BridgeIdentifier) == valuePri) return 0; SET_PRIORITY_IN_IDENTIFIER(valuePri, tree->BridgeIdentifier); tree->BridgePriority.RootID = tree->BridgePriority.RRootID = tree->BridgePriority.DesignatedBridgeID = tree->BridgeIdentifier; /* 12.8.1.4.4 do not require reselect, but I think it is needed, * because 12.8.1.3.4.c) requires it */ FOREACH_PTP_IN_TREE(ptp, tree) { ptp->selected = false; ptp->reselect = true; } return 0; } /* 12.8.2.1 Read CIST Port Parameters */ void MSTP_IN_get_cist_port_status(port_t *prt, CIST_PortStatus *status) { per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); /* 12.8.2.2.3 b) */ status->uptime = (signed int)((prt->bridge)->uptime) - (signed int)(cist->start_time); status->state = cist->state; assign(status->port_id, cist->portId); assign(status->admin_external_port_path_cost, prt->AdminExternalPortPathCost); assign(status->external_port_path_cost, prt->ExternalPortPathCost); assign(status->designated_root, cist->portPriority.RootID); assign(status->designated_external_cost, __be32_to_cpu(cist->portPriority.ExtRootPathCost)); assign(status->designated_bridge, cist->portPriority.DesignatedBridgeID); assign(status->designated_port, cist->portPriority.DesignatedPortID); assign(status->designated_regional_root, cist->portPriority.RRootID); assign(status->designated_internal_cost, __be32_to_cpu(cist->portPriority.IntRootPathCost)); status->tc_ack = prt->tcAck; assign(status->port_hello_time, cist->portTimes.Hello_Time); status->admin_edge_port = prt->AdminEdgePort; status->auto_edge_port = prt->AutoEdge; status->oper_edge_port = prt->operEdge; status->enabled = prt->portEnabled; status->admin_p2p = prt->AdminP2P; status->oper_p2p = prt->operPointToPointMAC; status->restricted_role = prt->restrictedRole; status->restricted_tcn = prt->restrictedTcn; status->role = cist->role; status->disputed = cist->disputed; assign(status->admin_internal_port_path_cost, cist->AdminInternalPortPathCost); assign(status->internal_port_path_cost, cist->InternalPortPathCost); status->bpdu_guard_port = prt->BpduGuardPort; status->bpdu_guard_error = prt->BpduGuardError; status->network_port = prt->NetworkPort; status->ba_inconsistent = prt->BaInconsistent; status->bpdu_filter_port = prt->bpduFilterPort; status->num_rx_bpdu_filtered = prt->num_rx_bpdu_filtered; status->num_rx_bpdu = prt->num_rx_bpdu; status->num_rx_tcn = prt->num_rx_tcn; status->num_tx_bpdu = prt->num_tx_bpdu; status->num_tx_tcn = prt->num_tx_tcn; status->num_trans_fwd = prt->num_trans_fwd; status->num_trans_blk = prt->num_trans_blk; status->rcvdBpdu = prt->rcvdBpdu; status->rcvdRSTP = prt->rcvdRSTP; status->rcvdSTP = prt->rcvdSTP; status->rcvdTcAck = prt->rcvdTcAck; status->rcvdTcn = prt->rcvdTcn; status->sendRSTP = prt->sendRSTP; } /* 12.8.2.2 Read MSTI Port Parameters */ void MSTP_IN_get_msti_port_status(per_tree_port_t *ptp, MSTI_PortStatus *status) { status->uptime = (signed int)((ptp->port->bridge)->uptime) - (signed int)(ptp->start_time); status->state = ptp->state; assign(status->port_id, ptp->portId); assign(status->admin_internal_port_path_cost, ptp->AdminInternalPortPathCost); assign(status->internal_port_path_cost, ptp->InternalPortPathCost); assign(status->designated_regional_root, ptp->portPriority.RRootID); assign(status->designated_internal_cost, __be32_to_cpu(ptp->portPriority.IntRootPathCost)); assign(status->designated_bridge, ptp->portPriority.DesignatedBridgeID); assign(status->designated_port, ptp->portPriority.DesignatedPortID); status->role = ptp->role; status->disputed = ptp->disputed; } /* 12.8.2.3 Set CIST port parameters */ int MSTP_IN_set_cist_port_config(port_t *prt, CIST_PortConfig *cfg) { bool changed; __u32 new_ExternalPathCost; bool new_p2p; per_tree_port_t *cist; bridge_t *br = prt->bridge; /* Firstly, validation */ if(cfg->set_admin_p2p) { switch(cfg->admin_p2p) { case p2pAuto: case p2pForceTrue: case p2pForceFalse: break; default: cfg->admin_p2p = p2pAuto; } } if(cfg->set_admin_external_port_path_cost) { if(MAX_PATH_COST < cfg->admin_external_port_path_cost) { ERROR_PRTNAME(br, prt, "Port Path Cost must be between 0 and 200000000"); return -1; } } /* Secondly, do set */ changed = false; if(cfg->set_admin_external_port_path_cost) { prt->AdminExternalPortPathCost = cfg->admin_external_port_path_cost; new_ExternalPathCost = (0 == prt->AdminExternalPortPathCost) ? compute_pcost(GET_PORT_SPEED(prt)) : prt->AdminExternalPortPathCost; if(prt->ExternalPortPathCost != new_ExternalPathCost) { assign(prt->ExternalPortPathCost, new_ExternalPathCost); changed = true; /* 12.8.2.3.4 */ cist = GET_CIST_PTP_FROM_PORT(prt); cist->selected = false; cist->reselect = true; } } if(cfg->set_admin_p2p) { prt->AdminP2P = cfg->admin_p2p; switch(prt->AdminP2P) { case p2pForceTrue: new_p2p = true; break; case p2pForceFalse: new_p2p = false; break; case p2pAuto: default: new_p2p = !!GET_PORT_DUPLEX(prt); break; } if(prt->operPointToPointMAC != new_p2p) { prt->operPointToPointMAC = new_p2p; changed = true; } } if(cfg->set_admin_edge_port) { if(prt->AdminEdgePort != cfg->admin_edge_port) { prt->AdminEdgePort = cfg->admin_edge_port; BDSM_begin(prt); changed = true; } } if(cfg->set_auto_edge_port) { if(prt->AutoEdge != cfg->auto_edge_port) { prt->AutoEdge = cfg->auto_edge_port; changed = true; } } if(cfg->set_restricted_role) { if(prt->restrictedRole != cfg->restricted_role) { prt->restrictedRole = cfg->restricted_role; changed = true; } } if(cfg->set_restricted_tcn) { if(prt->restrictedTcn != cfg->restricted_tcn) { prt->restrictedTcn = cfg->restricted_tcn; changed = true; } } if(cfg->set_bpdu_guard_port) { if(prt->BpduGuardPort != cfg->bpdu_guard_port) { prt->BpduGuardPort = cfg->bpdu_guard_port; INFO_PRTNAME(br, prt,"BpduGuardPort new=%d", prt->BpduGuardPort); } } if(cfg->set_network_port) { if(prt->NetworkPort != cfg->network_port) { prt->NetworkPort = cfg->network_port; INFO_PRTNAME(br, prt, "NetworkPort new=%d", prt->NetworkPort); /* When Network port config is removed and bridge assurance * inconsistency is set, clear the inconsistency. */ if(!prt->NetworkPort && prt->BaInconsistent) { prt->BaInconsistent = false; INFO_PRTNAME(br, prt, "Clear Bridge assurance inconsistency"); } changed = true; } } if(cfg->set_dont_txmt) { if(prt->dontTxmtBpdu != cfg->dont_txmt) { prt->dontTxmtBpdu = cfg->dont_txmt; INFO_PRTNAME(br, prt, "donttxmt new=%d", prt->dontTxmtBpdu); } } if(cfg->set_bpdu_filter_port) { if (prt->bpduFilterPort != cfg->bpdu_filter_port) { prt->bpduFilterPort = cfg->bpdu_filter_port; prt->num_rx_bpdu_filtered = 0; INFO_PRTNAME(br, prt,"bpduFilterPort new=%d", prt->bpduFilterPort); } } if(changed && prt->portEnabled) br_state_machines_run(prt->bridge); return 0; } /* 12.8.2.4 Set MSTI port parameters */ int MSTP_IN_set_msti_port_config(per_tree_port_t *ptp, MSTI_PortConfig *cfg) { __u8 valuePri; __u32 new_InternalPathCost; bool changed = false; port_t *prt = ptp->port; bridge_t *br = prt->bridge; if(cfg->set_port_priority) { if(15 < cfg->port_priority) { ERROR_MSTINAME(br, prt, ptp, "Port Priority must be between 0 and 15"); return -1; } } if(cfg->set_admin_internal_port_path_cost) { if (MAX_PATH_COST < cfg->admin_internal_port_path_cost) { ERROR_MSTINAME(br, prt, ptp, "Port Path Cost must be between 0 and 200000000"); return -1; } } if(cfg->set_port_priority) { valuePri = cfg->port_priority << 4; if(GET_PRIORITY_FROM_IDENTIFIER(ptp->portId) != valuePri) { SET_PRIORITY_IN_IDENTIFIER(valuePri, ptp->portId); changed = true; } } if(cfg->set_admin_internal_port_path_cost) { ptp->AdminInternalPortPathCost = cfg->admin_internal_port_path_cost; new_InternalPathCost = (0 == ptp->AdminInternalPortPathCost) ? compute_pcost(GET_PORT_SPEED(prt)) : ptp->AdminInternalPortPathCost; if(ptp->InternalPortPathCost != new_InternalPathCost) { assign(ptp->InternalPortPathCost, new_InternalPathCost); changed = true; } } if(changed && prt->portEnabled) { /* 12.8.2.4.4 */ ptp->selected = false; ptp->reselect = true; br_state_machines_run(br); } return 0; } /* 12.8.2.5 Force BPDU Migration Check */ int MSTP_IN_port_mcheck(port_t *prt) { bridge_t *br = prt->bridge; per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); if(rstpVersion(br) && prt->portEnabled && br->bridgeEnabled) { prt->mcheck = true; cist->proposing = true; br_state_machines_run(br); } return 0; } /* 12.10.3.8 Set VID to FID allocation */ bool MSTP_IN_set_vid2fid(bridge_t *br, __u16 vid, __u16 fid) { bool vid2mstid_changed; if((vid < 1) || (vid > MAX_VID) || (fid > MAX_FID)) { ERROR_BRNAME(br, "Error allocating VID(%hu) to FID(%hu)", vid, fid); return false; } vid2mstid_changed = (br->fid2mstid[fid] != br->fid2mstid[br->vid2fid[vid]]); br->vid2fid[vid] = fid; if(vid2mstid_changed) { RecalcConfigDigest(br); br_state_machines_begin(br); } return true; } /* Set all VID-to-FID mappings at once */ bool MSTP_IN_set_all_vids2fids(bridge_t *br, __u16 *vids2fids) { bool vid2mstid_changed; int vid; vid2mstid_changed = false; for(vid = 1; vid <= MAX_VID; ++vid) { if(vids2fids[vid] > MAX_FID) { /* Incorrect value == keep prev value */ vids2fids[vid] = br->vid2fid[vid]; continue; } if(br->fid2mstid[vids2fids[vid]] != br->fid2mstid[br->vid2fid[vid]]) vid2mstid_changed = true; } memcpy(br->vid2fid, vids2fids, sizeof(br->vid2fid)); if(vid2mstid_changed) { RecalcConfigDigest(br); br_state_machines_begin(br); } return true; } /* 12.12.2.2 Set FID to MSTID allocation */ bool MSTP_IN_set_fid2mstid(bridge_t *br, __u16 fid, __u16 mstid) { tree_t *tree; __be16 MSTID; bool found; int vid; if(fid > MAX_FID) { ERROR_BRNAME(br, "Bad FID(%hu)", fid); return false; } MSTID = __cpu_to_be16(mstid); found = false; FOREACH_TREE_IN_BRIDGE(tree, br) { if(tree->MSTID == MSTID) { found = true; break; } } if(!found) { ERROR_BRNAME(br, "MSTID(%hu) not found", mstid); return false; } if(br->fid2mstid[fid] != MSTID) { br->fid2mstid[fid] = MSTID; /* check if there are VLANs using this FID */ for(vid = 1; vid <= MAX_VID; ++vid) { if(br->vid2fid[vid] == fid) { RecalcConfigDigest(br); br_state_machines_begin(br); break; } } } return true; } /* Set all FID-to-MSTID mappings at once */ bool MSTP_IN_set_all_fids2mstids(bridge_t *br, __u16 *fids2mstids) { tree_t *tree; __be16 MSTID[MAX_FID + 1]; bool found, vid2mstid_changed; int fid, vid; __be16 prev_vid2mstid[MAX_VID + 2]; for(fid = 0; fid <= MAX_FID; ++fid) { if(fids2mstids[fid] > MAX_MSTID) { /* Incorrect value == keep prev value */ fids2mstids[fid] = __be16_to_cpu(MSTID[fid] = br->fid2mstid[fid]); } else MSTID[fid] = __cpu_to_be16(fids2mstids[fid]); found = false; FOREACH_TREE_IN_BRIDGE(tree, br) { if(tree->MSTID == MSTID[fid]) { found = true; break; } } if(!found) { ERROR_BRNAME(br, "Error allocating FID(%hu) to MSTID(%hu): MSTID not found", fid, fids2mstids[fid]); return false; } } for(vid = 1; vid <= MAX_VID; ++vid) prev_vid2mstid[vid] = br->fid2mstid[br->vid2fid[vid]]; memcpy(br->fid2mstid, MSTID, sizeof(br->fid2mstid)); vid2mstid_changed = false; for(vid = 1; vid <= MAX_VID; ++vid) { if(prev_vid2mstid[vid] != br->fid2mstid[br->vid2fid[vid]]) { vid2mstid_changed = true; break; } } if(vid2mstid_changed) { RecalcConfigDigest(br); br_state_machines_begin(br); } return true; } /* 12.12.1.1 Read MSTI List */ bool MSTP_IN_get_mstilist(bridge_t *br, int *num_mstis, __u16 *mstids) { tree_t *tree; *num_mstis = 0; FOREACH_TREE_IN_BRIDGE(tree, br) { mstids[*num_mstis] = __be16_to_cpu(tree->MSTID); /* Check for "<", not for "<=", as num_mstis include CIST */ if(MAX_IMPLEMENTATION_MSTIS < ++(*num_mstis)) break; } return true; } /* 12.12.1.2 Create MSTI */ bool MSTP_IN_create_msti(bridge_t *br, __u16 mstid) { tree_t *tree, *tree_after, *new_tree; per_tree_port_t *ptp, *nxt, *ptp_after, *new_ptp; int num_of_mstis; __be16 MSTID; if((mstid < 1) || (mstid > MAX_MSTID)) { ERROR_BRNAME(br, "Bad MSTID(%hu)", mstid); return false; } MSTID = __cpu_to_be16(mstid); /* Find place where to insert new MSTID. * Also check if such MSTID is already in the list. * Also count existing mstis. */ tree_after = NULL; num_of_mstis = 0; FOREACH_TREE_IN_BRIDGE(tree, br) { if(tree->MSTID == MSTID) { INFO_BRNAME(br, "MSTID(%hu) is already in the list", mstid); return true; /* yes, it is success */ } if(cmp(tree->MSTID, <, MSTID)) tree_after = tree; ++num_of_mstis; } /* Sanity check */ if(NULL == tree_after) { ERROR_BRNAME(br, "Can't add MSTID(%hu): no CIST in the list", mstid); return false; } /* End of Sanity check */ /* Check for "<", not for "<=", as num_of_mstis include CIST */ if(MAX_IMPLEMENTATION_MSTIS < num_of_mstis) { ERROR_BRNAME(br, "Can't add MSTID(%hu): maximum count(%u) reached", mstid, MAX_IMPLEMENTATION_MSTIS); return false; } /* Create new tree and its list of PerTreePort structures */ tree = GET_CIST_TREE(br); if(!(new_tree=create_tree(br,tree->BridgeIdentifier.s.mac_address,MSTID))) return false; FOREACH_PTP_IN_TREE(ptp_after, tree_after) { if(!(new_ptp = create_ptp(new_tree, ptp_after->port))) { /* Remove and free all previously created entries in tree's list */ list_for_each_entry_safe(ptp, nxt, &new_tree->ports, tree_list) { list_del(&ptp->port_list); list_del(&ptp->tree_list); free(ptp); } return false; } list_add(&new_ptp->port_list, &ptp_after->port_list); list_add_tail(&new_ptp->tree_list, &new_tree->ports); } list_add(&new_tree->bridge_list, &tree_after->bridge_list); /* There are no FIDs allocated to this MSTID, so VID-to-MSTID mapping * did not change. So, no need in RecalcConfigDigest. * Just initialize state machines for this tree. */ tree_state_machines_begin(new_tree); return true; } /* 12.12.1.3 Delete MSTI */ bool MSTP_IN_delete_msti(bridge_t *br, __u16 mstid) { tree_t *tree; per_tree_port_t *ptp, *nxt; int fid; bool found; __be16 MSTID = __cpu_to_be16(mstid); if((mstid < 1) || (mstid > MAX_MSTID)) { ERROR_BRNAME(br, "Bad MSTID(%hu)", mstid); return false; } /* Check if there are FIDs associated with this MSTID */ for(fid = 0; fid <= MAX_FID; ++fid) { if(br->fid2mstid[fid] == MSTID) { ERROR_BRNAME(br, "Can't delete MSTID(%hu): there are FIDs allocated to it", mstid); return false; } } found = false; FOREACH_TREE_IN_BRIDGE(tree, br) { if(tree->MSTID == MSTID) { found = true; break; } } if(!found) { INFO_BRNAME(br, "MSTID(%hu) is not in the list", mstid); return true; /* yes, it is success */ } list_del(&tree->bridge_list); list_for_each_entry_safe(ptp, nxt, &tree->ports, tree_list) { list_del(&ptp->port_list); list_del(&ptp->tree_list); free(ptp); } free(tree); /* There are no FIDs allocated to this MSTID, so VID-to-MSTID mapping * did not change. So, no need in RecalcConfigDigest. * Give state machine a spare run, just for the case... */ br_state_machines_run(br); return true; } /* 12.12.3.4 Set MST Configuration Identifier Elements */ void MSTP_IN_set_mst_config_id(bridge_t *br, __u16 revision, __u8 *name) { __be16 valueRevision = __cpu_to_be16(revision); bool changed = (0 != strncmp((char *)name, (char *)br->MstConfigId.s.configuration_name, sizeof(br->MstConfigId.s.configuration_name)) ) || (valueRevision != br->MstConfigId.s.revision_level); if(changed) { assign(br->MstConfigId.s.revision_level, valueRevision); memset(br->MstConfigId.s.configuration_name, 0, sizeof(br->MstConfigId.s.configuration_name)); strncpy((char *)br->MstConfigId.s.configuration_name, (char *)name, sizeof(br->MstConfigId.s.configuration_name) - 1); br_state_machines_begin(br); } } /* * If hint_SetToYes == true, some tcWhile in this tree has non-zero value. * If hint_SetToYes == false, some tcWhile in this tree has just became zero, * so we should check all other tcWhile's in this tree. */ static void set_TopologyChange(tree_t *tree, bool hint_SetToYes, port_t *port) { per_tree_port_t *ptp; bool prev_tc_not_set = !tree->topology_change; if(hint_SetToYes) { tree->topology_change = true; tree->time_since_topology_change = 0; if(prev_tc_not_set) ++(tree->topology_change_count); strncpy(tree->topology_change_port, tree->last_topology_change_port, IFNAMSIZ); strncpy(tree->last_topology_change_port, port->sysdeps.name, IFNAMSIZ); return; } /* Some tcWhile has just became zero. Check if we need reset * topology_change flag */ if(prev_tc_not_set) return; tree->topology_change = false; FOREACH_PTP_IN_TREE(ptp, tree) { if(0 != ptp->tcWhile) { tree->topology_change = true; tree->time_since_topology_change = 0; return; } } } /* Helper functions, compare two priority vectors */ static bool samePriorityAndTimers(port_priority_vector_t *vec1, port_priority_vector_t *vec2, times_t *time1, times_t *time2, bool cist) { if(cist) { if(cmp(time1->Forward_Delay, !=, time2->Forward_Delay)) return false; if(cmp(time1->Max_Age, !=, time2->Max_Age)) return false; if(cmp(time1->Message_Age, !=, time2->Message_Age)) return false; if(cmp(time1->Hello_Time, !=, time2->Hello_Time)) return false; if(cmp(vec1->RootID, !=, vec2->RootID)) return false; if(cmp(vec1->ExtRootPathCost, !=, vec2->ExtRootPathCost)) return false; } if(cmp(time1->remainingHops, !=, time2->remainingHops)) return false; if(cmp(vec1->RRootID, !=, vec2->RRootID)) return false; if(cmp(vec1->IntRootPathCost, !=, vec2->IntRootPathCost)) return false; if(cmp(vec1->DesignatedBridgeID, !=, vec2->DesignatedBridgeID)) return false; if(cmp(vec1->DesignatedPortID, !=, vec2->DesignatedPortID)) return false; return true; } static bool betterorsamePriority(port_priority_vector_t *vec1, port_priority_vector_t *vec2, port_identifier_t pId1, port_identifier_t pId2, bool cist) { int result; if(cist) { if(0 < (result = _ncmp(vec1->RootID, vec2->RootID))) return false; /* worse */ else if(0 > result) return true; /* better */ /* The same. Check further. */ if(0 < (result = _ncmp(vec1->ExtRootPathCost, vec2->ExtRootPathCost))) return false; /* worse */ else if(0 > result) return true; /* better */ /* The same. Check further. */ } if(0 < (result = _ncmp(vec1->RRootID, vec2->RRootID))) return false; /* worse */ else if(0 > result) return true; /* better */ /* The same. Check further. */ if(0 < (result = _ncmp(vec1->IntRootPathCost, vec2->IntRootPathCost))) return false; /* worse */ else if(0 > result) return true; /* better */ /* The same. Check further. */ if(0 < (result = _ncmp(vec1->DesignatedBridgeID, vec2->DesignatedBridgeID))) return false; /* worse */ else if(0 > result) return true; /* better */ /* The same. Check further. */ if(0 < (result = _ncmp(vec1->DesignatedPortID, vec2->DesignatedPortID))) return false; /* worse */ else if(0 > result) return true; /* better */ /* The same. Check further. */ /* Port ID is a tie-breaker */ return cmp(pId1, <=, pId2); } /* 13.26.1 betterorsameInfo */ static bool betterorsameInfo(per_tree_port_t *ptp, port_info_origin_t newInfoIs) { if((ioReceived == newInfoIs) && (ioReceived == ptp->infoIs)) return betterorsamePriority(&ptp->msgPriority, &ptp->portPriority, 0, 0, (0 == ptp->MSTID)); else if((ioMine == newInfoIs) && (ioMine == ptp->infoIs)) return betterorsamePriority(&ptp->designatedPriority, &ptp->portPriority, 0, 0, (0 == ptp->MSTID)); return false; } /* 13.26.2 clearAllRcvdMsgs */ static bool clearAllRcvdMsgs(port_t *prt, bool dry_run) { per_tree_port_t *ptp; if(dry_run) { FOREACH_PTP_IN_PORT(ptp, prt) if(ptp->rcvdMsg) return true; return false; } FOREACH_PTP_IN_PORT(ptp, prt) ptp->rcvdMsg = false; return false; } /* 13.26.3 clearReselectTree */ static void clearReselectTree(tree_t *tree) { per_tree_port_t *ptp; FOREACH_PTP_IN_TREE(ptp, tree) ptp->reselect = false; } /* 13.26.4 fromSameRegion */ static bool fromSameRegion(port_t *prt) { /* Check for rcvdRSTP is superfluous here */ if((protoMSTP > prt->rcvdBpduData.protocolVersion)/* || (!prt->rcvdRSTP)*/) return false; return cmp(prt->bridge->MstConfigId, ==, prt->rcvdBpduData.mstConfigurationIdentifier); } /* 13.26.5 newTcWhile */ static void newTcWhile(per_tree_port_t *ptp) { if(0 != ptp->tcWhile) return; tree_t *tree = ptp->tree; port_t *prt = ptp->port; if(prt->sendRSTP) { per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); ptp->tcWhile = cist->portTimes.Hello_Time + 1; set_TopologyChange(tree, true, prt); if(0 == ptp->MSTID) prt->newInfo = true; else prt->newInfoMsti = true; return; } times_t *times = &tree->rootTimes; ptp->tcWhile = times->Max_Age + times->Forward_Delay; set_TopologyChange(tree, true, prt); } /* 13.26.6 rcvInfo */ static port_info_t rcvInfo(per_tree_port_t *ptp) { msti_configuration_message_t *msti_msg; per_tree_port_t *ptp_1; bool roleIsDesignated, cist; bool msg_Better_port, msg_SamePriorityAndTimers_port; port_priority_vector_t *mPri = &(ptp->msgPriority); times_t *mTimes = &(ptp->msgTimes); port_t *prt = ptp->port; bpdu_t *b = &(prt->rcvdBpduData); if(bpduTypeTCN == b->bpduType) { prt->rcvdTcn = true; FOREACH_PTP_IN_PORT(ptp_1, prt) ptp_1->rcvdTc = true; return OtherInfo; } if(0 == ptp->MSTID) { /* CIST */ if(protoSTP != b->protocolVersion) { switch(BPDU_FLAGS_ROLE_GET(b->flags)) { case encodedRoleAlternateBackup: case encodedRoleRoot: roleIsDesignated = false; break; case encodedRoleDesignated: roleIsDesignated = true; break; case encodedRoleMaster: /* 802.1D-2004 S9.2.9 P61. The Unknown value of Port Role * cannot be generated by a valid implementation; however, * this value is accepted on receipt. roleMaster in MSTP is * roleUnknown in RSTP. * NOTE.If the Unknown value of the Port Role parameter is * received, the state machines will effectively treat the RST * BPDU as if it were a Configuration BPDU */ if(protoRSTP == b->protocolVersion) { roleIsDesignated = true; break; } else { return OtherInfo; } break; default: return OtherInfo; } } else { /* 13.26.6.NOTE: A Configuration BPDU implicitly conveys a * Designated Port Role */ roleIsDesignated = true; } cist = true; assign(mPri->RRootID, b->cistRRootID); assign(mPri->DesignatedPortID, b->cistPortID); assign(mPri->RootID, b->cistRootID); assign(mPri->ExtRootPathCost, b->cistExtRootPathCost); /* messageTimes */ #define NEAREST_WHOLE_SECOND(msgTime) \ ((128 > msgTime[1]) ? msgTime[0] : msgTime[0] + 1) mTimes->Forward_Delay = NEAREST_WHOLE_SECOND(b->ForwardDelay); mTimes->Max_Age = NEAREST_WHOLE_SECOND(b->MaxAge); mTimes->Message_Age = NEAREST_WHOLE_SECOND(b->MessageAge); mTimes->Hello_Time = NEAREST_WHOLE_SECOND(b->HelloTime); if(protoMSTP > b->protocolVersion) { /* STP Configuration BPDU or RST BPDU */ assign(mPri->IntRootPathCost, __constant_cpu_to_be32(0)); assign(mPri->DesignatedBridgeID, b->cistRRootID); /* messageTimes.remainingHops */ assign(mTimes->remainingHops, prt->bridge->MaxHops); } else { /* MST BPDU */ assign(mPri->IntRootPathCost, b->cistIntRootPathCost); assign(mPri->DesignatedBridgeID, b->cistBridgeID); /* messageTimes.remainingHops */ assign(mTimes->remainingHops, b->cistRemainingHops); } } else { /* MSTI */ if(protoMSTP > b->protocolVersion) return OtherInfo; msti_msg = ptp->rcvdMstiConfig; switch(BPDU_FLAGS_ROLE_GET(msti_msg->flags)) { case encodedRoleAlternateBackup: case encodedRoleRoot: roleIsDesignated = false; break; case encodedRoleDesignated: roleIsDesignated = true; break; default: return OtherInfo; } cist = false; assign(mPri->RRootID, msti_msg->mstiRRootID); assign(mPri->IntRootPathCost, msti_msg->mstiIntRootPathCost); /* Build MSTI DesignatedBridgeID */ assign(mPri->DesignatedBridgeID, b->cistBridgeID); assign(mPri->DesignatedBridgeID.s.priority, ptp->MSTID); SET_PRIORITY_IN_IDENTIFIER(msti_msg->bridgeIdentifierPriority, mPri->DesignatedBridgeID); /* Build MSTI DesignatedPortID */ assign(mPri->DesignatedPortID, b->cistPortID); SET_PRIORITY_IN_IDENTIFIER(msti_msg->portIdentifierPriority, mPri->DesignatedPortID); /* messageTimes */ assign(mTimes->remainingHops, msti_msg->remainingHops); } msg_Better_port = !betterorsamePriority(&(ptp->portPriority), mPri, 0, 0, cist); if(roleIsDesignated) { /* a).1) */ if(msg_Better_port || ((0 == memcmp(mPri->DesignatedBridgeID.s.mac_address, ptp->portPriority.DesignatedBridgeID.s.mac_address, ETH_ALEN) ) && (0 == ((mPri->DesignatedPortID ^ ptp->portPriority.DesignatedPortID ) & __constant_cpu_to_be16(0x0FFF) ) ) ) ) return SuperiorDesignatedInfo; /* a).2) */ /* We already know that msgPriority _IS_NOT_BETTER_than portPriority. * So, if msgPriority _IS_SAME_OR_BETTER_than portPriority then * msgPriority _IS_SAME_as portPriority. */ msg_SamePriorityAndTimers_port = samePriorityAndTimers(mPri, &(ptp->portPriority), mTimes, &(ptp->portTimes), cist); if((!msg_SamePriorityAndTimers_port) && betterorsamePriority(mPri, &(ptp->portPriority), 0, 0, cist) ) return SuperiorDesignatedInfo; /* b) */ if(msg_SamePriorityAndTimers_port && (ioReceived == ptp->infoIs)) return RepeatedDesignatedInfo; /* c) */ return InferiorDesignatedInfo; } /* d) */ if(!msg_Better_port) return InferiorRootAlternateInfo; return OtherInfo; } /* 13.26.7 recordAgreement */ static void recordAgreement(per_tree_port_t *ptp) { bool cist_agreed, cist_proposing; per_tree_port_t *cist; port_t *prt = ptp->port; bpdu_t *b = &(prt->rcvdBpduData); if(0 == ptp->MSTID) { /* CIST */ if(rstpVersion(prt->bridge) && prt->operPointToPointMAC && (b->flags & (1 << offsetAgreement)) ) { ptp->agreed = true; ptp->proposing = false; } else ptp->agreed = false; cist_agreed = ptp->agreed; cist_proposing = ptp->proposing; if(!prt->rcvdInternal) list_for_each_entry_continue(ptp, &prt->trees, port_list) { ptp->agreed = cist_agreed; ptp->proposing = cist_proposing; } return; } /* MSTI */ cist = GET_CIST_PTP_FROM_PORT(prt); if(prt->operPointToPointMAC && cmp(b->cistRootID, ==, cist->portPriority.RootID) && cmp(b->cistExtRootPathCost, ==, cist->portPriority.ExtRootPathCost) && cmp(b->cistRRootID, ==, cist->portPriority.RRootID) && (ptp->rcvdMstiConfig->flags & (1 << offsetAgreement)) ) { ptp->agreed = true; ptp->proposing = false; } else ptp->agreed = false; } /* 13.26.8 recordDispute */ static void recordDispute(per_tree_port_t *ptp) { port_t *prt; if(0 == ptp->MSTID) { /* CIST */ prt = ptp->port; /* 802.1Q-2005(-2011) is somewhat unclear for the case * (!prt->rcvdInternal): if we should record dispute for all MSTIs * unconditionally or only when CIST Learning flag is set in BPDU. * I guess that in this case MSTIs should be in sync with CIST * so record dispute for the MSTIs only when the same is done for CIST. * Additional supporting argument to this guess is that in * setTcFlags() we do the same. * But that is only a guess and I could be wrong here ;) */ if(prt->rcvdBpduData.flags & (1 << offsetLearnig)) { ptp->disputed = true; ptp->agreed = false; if(!prt->rcvdInternal) list_for_each_entry_continue(ptp, &prt->trees, port_list) { ptp->disputed = true; ptp->agreed = false; } } return; } /* MSTI */ if(ptp->rcvdMstiConfig->flags & (1 << offsetLearnig)) { ptp->disputed = true; ptp->agreed = false; } } /* 13.26.9 recordMastered */ static void recordMastered(per_tree_port_t *ptp) { port_t *prt = ptp->port; if(0 == ptp->MSTID) { /* CIST */ if(!prt->rcvdInternal) list_for_each_entry_continue(ptp, &prt->trees, port_list) ptp->mastered = false; return; } /* MSTI */ ptp->mastered = prt->operPointToPointMAC && (ptp->rcvdMstiConfig->flags & (1 << offsetMaster)); } /* 13.26.f) recordPriority */ static void recordPriority(per_tree_port_t *ptp) { assign(ptp->portPriority, ptp->msgPriority); } /* 13.26.10 recordProposal */ static void recordProposal(per_tree_port_t *ptp) { bool cist_proposed; port_t *prt; /* 802.1Q-2005 says to check if received message conveys * a Designated Port Role. But there is no need in this check, * as it is always true. This function is called only in two states: * PISM_SUPERIOR_DESIGNATED and PISM_REPEATED_DESIGNATED, which * can be entered only if rcvInfo returns * SuperiorDesignatedInfo or RepeatedDesignatedInfo. * Which in turn can only happen if message conveys designated role * (see rcvInfo). */ if(0 == ptp->MSTID) { /* CIST */ prt = ptp->port; if(prt->rcvdBpduData.flags & (1 << offsetProposal)) ptp->proposed = true; cist_proposed = ptp->proposed; if(!prt->rcvdInternal) list_for_each_entry_continue(ptp, &prt->trees, port_list) ptp->proposed = cist_proposed; return; } /* MSTI */ if(ptp->rcvdMstiConfig->flags & (1 << offsetProposal)) ptp->proposed = true; } /* 13.26.11 recordTimes */ static void recordTimes(per_tree_port_t *ptp) { /* 802.1Q-2005 and 802.1D-2004 both say that we have to copy * Hello_Time from msgTimes to portTimes. * 802.1Q-2011, on the other hand, says that Hello_Time should be set * to the default here. * As we have configurable Hello_Time, I choose the third option: * preserve the configured Hello_Time, It is in accordance with the * spirit of 802.1Q-2011, if we allow Hello_Time to be configurable. */ __u8 prev_Hello_Time = 0; assign(prev_Hello_Time, ptp->portTimes.Hello_Time); assign(ptp->portTimes, ptp->msgTimes); assign(ptp->portTimes.Hello_Time, prev_Hello_Time); } /* 13.24.s) + 17.19.7 of 802.1D : fdbFlush */ static void set_fdbFlush(per_tree_port_t *ptp) { port_t *prt = ptp->port; if(prt->operEdge || prt->deleted) { ptp->fdbFlush = false; return; } bridge_t *br = prt->bridge; if(rstpVersion(br)) { ptp->fdbFlush = true; ptp->calledFromFlushRoutine = true; MSTP_OUT_flush_all_fids(ptp); ptp->calledFromFlushRoutine = false; } else { per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); unsigned int FwdDelay = cist->designatedTimes.Forward_Delay; /* Initiate rapid ageing */ MSTP_OUT_set_ageing_time(prt, FwdDelay); assign(prt->rapidAgeingWhile, FwdDelay); ptp->fdbFlush = false; } } /* 13.26.12 setRcvdMsgs */ static void setRcvdMsgs(port_t *prt) { msti_configuration_message_t *msti_msg; int i; __be16 msg_MSTID; bool found; per_tree_port_t *ptp = GET_CIST_PTP_FROM_PORT(prt); ptp->rcvdMsg = true; /* 802.1Q-2005 says: * "Make the received CST or CIST message available to the CIST Port * Information state machines" * No need to do something special here, we already have rcvdBpduData. */ if(prt->rcvdInternal) { list_for_each_entry_continue(ptp, &prt->trees, port_list) { found = false; /* Find if message for this MSTI is conveyed in the BPDU */ for(i = 0, msti_msg = prt->rcvdBpduData.mstConfiguration; i < prt->rcvdBpduNumOfMstis; ++i, ++msti_msg) { msg_MSTID = msti_msg->mstiRRootID.s.priority & __constant_cpu_to_be16(0x0FFF); if(msg_MSTID == ptp->MSTID) { found = true; break; } } if(found) { ptp->rcvdMsg = true; /* 802.1Q-2005 says: * "Make available each MSTI message and the common parts of * the CIST message priority (the CIST Root Identifier, * External Root Path Cost and Regional Root Identifier) * to the Port Information state machine for that MSTI" * We set pointer to the MSTI configuration message for * fast access, while do not anything special for common * parts of the message, as the whole message is available * in rcvdBpduData. */ ptp->rcvdMstiConfig = msti_msg; } } } } /* 13.26.13 setReRootTree */ static void setReRootTree(tree_t *tree) { per_tree_port_t *ptp; FOREACH_PTP_IN_TREE(ptp, tree) ptp->reRoot = true; } /* 13.26.14 setSelectedTree */ static void setSelectedTree(tree_t *tree) { per_tree_port_t *ptp; /* * 802.1Q-2005 says that I should check "reselect" var * and take no action if it is "true" for any of the ports. * But there is no need in this check as setSelectedTree is called * only from PRSSM_to_ROLE_SELECTION, which is atomic, and it is called * in this sequence (13.33): * clearReselectTree(tree); * updtRolesTree(tree); * setSelectedTree(tree); * And we know that clearReselectTree resets "reselect" for all ports * and updtRolesTree() does not change value of "reselect". */ FOREACH_PTP_IN_TREE(ptp, tree) ptp->selected = true; } /* 13.26.15 setSyncTree */ static void setSyncTree(tree_t *tree) { per_tree_port_t *ptp; FOREACH_PTP_IN_TREE(ptp, tree) ptp->sync = true; } /* 13.26.16 setTcFlags */ static void setTcFlags(per_tree_port_t *ptp) { __u8 cistFlags; port_t *prt; if(0 == ptp->MSTID) { /* CIST */ prt = ptp->port; cistFlags = prt->rcvdBpduData.flags; if(cistFlags & (1 << offsetTcAck)) prt->rcvdTcAck = true; if(cistFlags & (1 << offsetTc)) { ptp->rcvdTc = true; if(!prt->rcvdInternal) list_for_each_entry_continue(ptp, &prt->trees, port_list) ptp->proposed = true; } return; } /* MSTI */ if(ptp->rcvdMstiConfig->flags & (1 << offsetTc)) ptp->rcvdTc = true; } /* 13.26.17 setTcPropTree */ static void setTcPropTree(per_tree_port_t *ptp) { per_tree_port_t *ptp_1; if(ptp->port->restrictedTcn) return; FOREACH_PTP_IN_TREE(ptp_1, ptp->tree) { if(ptp != ptp_1) ptp_1->tcProp = true; } } /* 13.26.18 syncMaster */ static void syncMaster(bridge_t *br) { per_tree_port_t *ptp; tree_t *tree = GET_CIST_TREE(br); /* For each MSTI */ list_for_each_entry_continue(tree, &br->trees, bridge_list) { FOREACH_PTP_IN_TREE(ptp, tree) { /* for each Port that has infoInternal set */ if(ptp->port->infoInternal) { ptp->agree = false; ptp->agreed = false; ptp->synced = false; ptp->sync = true; } } } } /* 13.26.19 txConfig */ static void txConfig(port_t *prt) { bpdu_t b; per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); if(prt->deleted || (roleDisabled == cist->role) || prt->dontTxmtBpdu) return; b.protocolIdentifier = 0; b.protocolVersion = protoSTP; b.bpduType = bpduTypeConfig; /* Standard says "tcWhile ... for the Port". Which one tcWhile? * I guess that this means tcWhile for the CIST. * But that is only a guess and I could be wrong here ;) */ b.flags = (0 != cist->tcWhile) ? (1 << offsetTc) : 0; if(prt->tcAck) b.flags |= (1 << offsetTcAck); assign(b.cistRootID, cist->designatedPriority.RootID); assign(b.cistExtRootPathCost, cist->designatedPriority.ExtRootPathCost); assign(b.cistRRootID, cist->designatedPriority.DesignatedBridgeID); assign(b.cistPortID, cist->designatedPriority.DesignatedPortID); b.MessageAge[0] = cist->designatedTimes.Message_Age; b.MessageAge[1] = 0; b.MaxAge[0] = cist->designatedTimes.Max_Age; b.MaxAge[1] = 0; b.HelloTime[0] = cist->portTimes.Hello_Time; /* ! use portTimes ! */ b.HelloTime[1] = 0; b.ForwardDelay[0] = cist->designatedTimes.Forward_Delay; b.ForwardDelay[1] = 0; MSTP_OUT_tx_bpdu(prt, &b, CONFIG_BPDU_SIZE); } static inline __u8 message_role_from_port_role(per_tree_port_t *ptp) { switch(ptp->role) { case roleRoot: return encodedRoleRoot; case roleDesignated: return encodedRoleDesignated; case roleAlternate: case roleBackup: return encodedRoleAlternateBackup; case roleMaster: return encodedRoleMaster; default: ERROR_PRTNAME(ptp->port->bridge, ptp->port, "Attempt to send from port with Disabled role"); return encodedRoleAlternateBackup; } } /* 802.1Q-2005: 13.26.20 txMstp * 802.1Q-2011: 13.27.27 txRstp */ static void txMstp(port_t *prt) { bpdu_t b; bridge_t *br = prt->bridge; per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); int msti_msgs_total_size; per_tree_port_t *ptp; msti_configuration_message_t *msti_msg; if(prt->deleted || (roleDisabled == cist->role) || prt->dontTxmtBpdu) return; b.protocolIdentifier = 0; b.bpduType = bpduTypeRST; /* Standard says "{tcWhile, agree, proposing} ... for the Port". * Which one {tcWhile, agree, proposing}? * I guess that this means {tcWhile, agree, proposing} for the CIST. * But that is only a guess and I could be wrong here ;) */ b.flags = BPDU_FLAGS_ROLE_SET(message_role_from_port_role(cist)); if(0 != cist->tcWhile) b.flags |= (1 << offsetTc); if(cist->proposing) b.flags |= (1 << offsetProposal); if(cist->learning) b.flags |= (1 << offsetLearnig); if(cist->forwarding) b.flags |= (1 << offsetForwarding); if(cist->agree) b.flags |= (1 << offsetAgreement); assign(b.cistRootID, cist->designatedPriority.RootID); assign(b.cistExtRootPathCost, cist->designatedPriority.ExtRootPathCost); assign(b.cistRRootID, cist->designatedPriority.RRootID); assign(b.cistPortID, cist->designatedPriority.DesignatedPortID); b.MessageAge[0] = cist->designatedTimes.Message_Age; b.MessageAge[1] = 0; b.MaxAge[0] = cist->designatedTimes.Max_Age; b.MaxAge[1] = 0; b.HelloTime[0] = cist->portTimes.Hello_Time; /* ! use portTimes ! */ b.HelloTime[1] = 0; b.ForwardDelay[0] = cist->designatedTimes.Forward_Delay; b.ForwardDelay[1] = 0; b.version1_len = 0; if(br->ForceProtocolVersion < protoMSTP) { b.protocolVersion = protoRSTP; MSTP_OUT_tx_bpdu(prt, &b, RST_BPDU_SIZE); return; } b.protocolVersion = protoMSTP; /* MST specific fields */ assign(b.mstConfigurationIdentifier, br->MstConfigId); assign(b.cistIntRootPathCost, cist->designatedPriority.IntRootPathCost); assign(b.cistBridgeID, cist->designatedPriority.DesignatedBridgeID); assign(b.cistRemainingHops, cist->designatedTimes.remainingHops); msti_msgs_total_size = 0; ptp = cist; msti_msg = b.mstConfiguration; /* 13.26.20.f) requires that msti configs should be inserted in * MSTID order. This is met by inserting trees in port's list of trees * in sorted (by MSTID) order (see MSTP_IN_create_msti) */ list_for_each_entry_continue(ptp, &prt->trees, port_list) { msti_msg->flags = BPDU_FLAGS_ROLE_SET(message_role_from_port_role(ptp)); if(0 != ptp->tcWhile) msti_msg->flags |= (1 << offsetTc); if(ptp->proposing) msti_msg->flags |= (1 << offsetProposal); if(ptp->learning) msti_msg->flags |= (1 << offsetLearnig); if(ptp->forwarding) msti_msg->flags |= (1 << offsetForwarding); if(ptp->agree) msti_msg->flags |= (1 << offsetAgreement); if(ptp->master) msti_msg->flags |= (1 << offsetMaster); assign(msti_msg->mstiRRootID, ptp->designatedPriority.RRootID); assign(msti_msg->mstiIntRootPathCost, ptp->designatedPriority.IntRootPathCost); msti_msg->bridgeIdentifierPriority = GET_PRIORITY_FROM_IDENTIFIER(ptp->designatedPriority.DesignatedBridgeID); msti_msg->portIdentifierPriority = GET_PRIORITY_FROM_IDENTIFIER(ptp->designatedPriority.DesignatedPortID); assign(msti_msg->remainingHops, ptp->designatedTimes.remainingHops); msti_msgs_total_size += sizeof(msti_configuration_message_t); ++msti_msg; } assign(b.version3_len, __cpu_to_be16(MST_BPDU_VER3LEN_WO_MSTI_MSGS + msti_msgs_total_size)); MSTP_OUT_tx_bpdu(prt, &b, MST_BPDU_SIZE_WO_MSTI_MSGS + msti_msgs_total_size); } /* 13.26.a) txTcn */ static void txTcn(port_t *prt) { bpdu_t b; per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); if(prt->deleted || (roleDisabled == cist->role) || prt->dontTxmtBpdu) return; b.protocolIdentifier = 0; b.protocolVersion = protoSTP; b.bpduType = bpduTypeTCN; MSTP_OUT_tx_bpdu(prt, &b, TCN_BPDU_SIZE); } /* 13.26.21 updtBPDUVersion */ static void updtBPDUVersion(port_t *prt) { if(protoRSTP <= prt->rcvdBpduData.protocolVersion) prt->rcvdRSTP = true; else prt->rcvdSTP = true; } /* 13.26.22 updtRcvdInfoWhile */ static void updtRcvdInfoWhile(per_tree_port_t *ptp) { port_t *prt = ptp->port; per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); unsigned int Message_Age = cist->portTimes.Message_Age; unsigned int Max_Age = cist->portTimes.Max_Age; unsigned int Hello_Time = cist->portTimes.Hello_Time; /* NOTE: 802.1Q-2005(-2011) says that we should use * "remainingHops ... from the CIST's portTimes parameter" * As for me this is clear oversight in the standard, * the remainingHops should be taken form the port's own portTimes, * not from CIST's. After all, if we don't use port's own * remainingHops here, they aren't used anywhere at all. * Besides, there is a scenario which breaks if we use CIST's * remainingHops here: * 1) Connect two switches (SW1,SW2) with two ports, thus forming a loop * 2) Configure them to be in the same region, with two trees: * 0 (CIST) and 1. * 3) at SW1# mstpctl settreeprio br0 1 4 * SW1 becomes regional root in tree 1 * 4) at SW2# mstpctl settreeprio br0 1 14 * 5) at SW1# mstpctl settreeprio br0 1 9 * * And now we have the classic "count-to-infinity" problem when the old * info ("Regional Root is SW1 with priority 4") circulates in the loop, * because it is better than current info ("Regional Root is SW1 with * priority 9"). The only way to get rid of that old info is * to age it out by the means of remainingHops counter. * In this situation we certainly must use counter from tree 1, * not CIST's. */ if((!prt->rcvdInternal && ((Message_Age + 1) <= Max_Age)) || (prt->rcvdInternal && (ptp->portTimes.remainingHops > 1)) ) ptp->rcvdInfoWhile = 3 * Hello_Time; else ptp->rcvdInfoWhile = 0; } static void updtbrAssuRcvdInfoWhile(port_t *prt) { per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); prt->brAssuRcvdInfoWhile = 3 * cist->portTimes.Hello_Time; } /* 13.26.24 updtRolesDisabledTree */ static void updtRolesDisabledTree(tree_t *tree) { per_tree_port_t *ptp; FOREACH_PTP_IN_TREE(ptp, tree) ptp->selectedRole = roleDisabled; } /* Aux function, not in standard. * Sets reselect for all MSTIs in the case CIST state for the port changes */ static void reselectMSTIs(port_t *prt) { per_tree_port_t *ptp = GET_CIST_PTP_FROM_PORT(prt); /* For each non-CIST ptp */ list_for_each_entry_continue(ptp, &prt->trees, port_list) ptp->reselect = true; } /* 13.26.23 updtRolesTree */ static void updtRolesTree(tree_t *tree) { per_tree_port_t *ptp, *root_ptp = NULL; port_priority_vector_t root_path_priority; bridge_identifier_t prevRRootID = tree->rootPriority.RRootID; __be32 prevExtRootPathCost = tree->rootPriority.ExtRootPathCost; __u32 newRootPathCost; bool cist = (0 == tree->MSTID); /* a), b) Select new root priority vector = {rootPriority, rootPortId} */ /* Initial value = bridge priority vector = {BridgePriority, 0} */ assign(tree->rootPriority, tree->BridgePriority); assign(tree->rootPortId, __constant_cpu_to_be16(0)); /* Now check root path priority vectors of all ports in tree and see if * there is a better vector */ FOREACH_PTP_IN_TREE(ptp, tree) { port_t *prt = ptp->port; /* 802.1Q says to calculate root priority vector only if port * is not Disabled, but check (infoIs == ioReceived) covers * the case (infoIs != ioDisabled). */ if((ioReceived == ptp->infoIs) && !prt->restrictedRole && cmp(ptp->portPriority.DesignatedBridgeID, !=, tree->BridgeIdentifier) ) { root_path_priority = ptp->portPriority; if(prt->rcvdInternal) { newRootPathCost = __be32_to_cpu(root_path_priority.IntRootPathCost); if(newRootPathCost > (UINT32_MAX - ptp->InternalPortPathCost)) newRootPathCost = UINT32_MAX; else newRootPathCost += ptp->InternalPortPathCost; assign(root_path_priority.IntRootPathCost, __cpu_to_be32(newRootPathCost)); } else if(cist) /* Yes, this check might be superfluous, * but I want to be on the safe side */ { newRootPathCost = __be32_to_cpu(root_path_priority.ExtRootPathCost); if(newRootPathCost > (UINT32_MAX - prt->ExternalPortPathCost)) newRootPathCost = UINT32_MAX; else newRootPathCost += prt->ExternalPortPathCost; assign(root_path_priority.ExtRootPathCost, __be32_to_cpu(newRootPathCost)); assign(root_path_priority.RRootID, tree->BridgeIdentifier); assign(root_path_priority.IntRootPathCost, __constant_cpu_to_be32(0)); } if(betterorsamePriority(&root_path_priority, &tree->rootPriority, ptp->portId, tree->rootPortId, cist)) { assign(tree->rootPriority, root_path_priority); assign(tree->rootPortId, ptp->portId); root_ptp = ptp; } } } /* 802.1q-2005 says, that at some point we need compare portTimes with * "... one for the Root Port ...". Bad IEEE! Why not mention explicit * var names??? (see 13.26.23.g) for instance) * These comparisons happen three times, twice in clause g) * and once in clause i). Look for samePriorityAndTimers() calls. * So, now I should guess what will work for the "times for the Root Port". * Thanks to Rajani's experiments I know for sure that I should use * designatedTimes here. Thank you, Rajani! * NOTE: Both Alex Rozin (author of rstplib) and Srinivas Aji (author * of rstpd) also compare portTimes with designatedTimes. */ /* c) Set new rootTimes */ if(root_ptp) { assign(tree->rootTimes, root_ptp->portTimes); port_t *prt = root_ptp->port; if(prt->rcvdInternal) { if(tree->rootTimes.remainingHops) --(tree->rootTimes.remainingHops); } else ++(tree->rootTimes.Message_Age); } else { assign(tree->rootTimes, tree->BridgeTimes); } FOREACH_PTP_IN_TREE(ptp, tree) { port_t *prt = ptp->port; /* d) Set new designatedPriority */ assign(ptp->designatedPriority, tree->rootPriority); assign(ptp->designatedPriority.DesignatedBridgeID, tree->BridgeIdentifier); assign(ptp->designatedPriority.DesignatedPortID, ptp->portId); /* I am not sure which condition to check here, as 802.1Q-2005 says: * "... If {Port} is attached to a LAN that has one or more STP Bridges * attached (as determined by the Port Protocol Migration state * machine) ..." -- why not to mention explicit var name? Bad IEEE. * But I guess that sendSTP (i.e. !sendRSTP) var will do ;) */ if(cist && !prt->sendRSTP) assign(ptp->designatedPriority.RRootID, tree->BridgeIdentifier); /* e) Set new designatedTimes */ assign(ptp->designatedTimes, tree->rootTimes); /* Keep the configured Hello_Time for the port. * NOTE: this is in accordance with the spirit of 802.1D-2004. * Also, this does not contradict 802.1Q-2005(-2011), as in these * standards both designatedTimes and rootTimes structures * don't have Hello_Time member. */ assign(ptp->designatedTimes.Hello_Time, ptp->portTimes.Hello_Time); } /* syncMaster */ if(cist && cmp(tree->rootPriority.RRootID, !=, prevRRootID) && ((0 != tree->rootPriority.ExtRootPathCost) || (0 != prevExtRootPathCost) ) ) syncMaster(tree->bridge); FOREACH_PTP_IN_TREE(ptp, tree) { port_t *prt = ptp->port; per_tree_port_t *cist_tree = GET_CIST_PTP_FROM_PORT(prt); /* f) Set Disabled role */ if(ioDisabled == ptp->infoIs) { ptp->selectedRole = roleDisabled; continue; } if(!cist && (ioReceived == cist_tree->infoIs) && !prt->infoInternal) { /* g) Set role for the boundary port in MSTI */ if(roleRoot == cist_tree->selectedRole) { ptp->selectedRole = roleMaster; if(!samePriorityAndTimers(&ptp->portPriority, &ptp->designatedPriority, &ptp->portTimes, &ptp->designatedTimes, /*cist*/ false)) ptp->updtInfo = true; continue; } /* Bad IEEE again! It says in 13.26.23 g) 2) that * MSTI state should follow CIST state only for the case of * Alternate port. This is obviously wrong! * In the descriptive clause 13.13 f) it says: * "At a Boundary Port frames allocated to the CIST and * all MSTIs are forwarded or not forwarded alike. * This is because Port Role assignments are such that * if the CIST Port Role is Root Port, the MSTI Port Role * will be Master Port, and if the CIST Port Role is * Designated Port, Alternate Port, Backup Port, * or Disabled Port, each MSTI’s Port Role will be the same." * So, ignore wrong 13.26.23 g) 2) and do as stated in 13.13 f) ! */ /* if(roleAlternate == cist_tree->selectedRole) */ { ptp->selectedRole = cist_tree->selectedRole; if(!samePriorityAndTimers(&ptp->portPriority, &ptp->designatedPriority, &ptp->portTimes, &ptp->designatedTimes, /*cist*/ false)) ptp->updtInfo = true; continue; } } else /* if(cist || (ioReceived != cist_tree->infoIs) || prt->infoInternal) */ { /* h) Set role for the aged info */ if(ioAged == ptp->infoIs) { ptp->selectedRole = roleDesignated; ptp->updtInfo = true; continue; } /* i) Set role for the mine info */ if(ioMine == ptp->infoIs) { ptp->selectedRole = roleDesignated; if(!samePriorityAndTimers(&ptp->portPriority, &ptp->designatedPriority, &ptp->portTimes, &ptp->designatedTimes, cist)) ptp->updtInfo = true; continue; } if(ioReceived == ptp->infoIs) { /* j) Set Root role */ if(root_ptp == ptp) { ptp->selectedRole = roleRoot; ptp->updtInfo = false; } else { if(betterorsamePriority(&ptp->portPriority, &ptp->designatedPriority, 0, 0, cist)) { if(cmp(ptp->portPriority.DesignatedBridgeID, !=, tree->BridgeIdentifier)) { /* k) Set Alternate role */ ptp->selectedRole = roleAlternate; } else { /* l) Set Backup role */ ptp->selectedRole = roleBackup; } /* reset updtInfo for both k) and l) */ ptp->updtInfo = false; } else /* designatedPriority is better than portPriority */ { /* m) Set Designated role */ ptp->selectedRole = roleDesignated; ptp->updtInfo = true; } } /* This is not in standard. But we really should set here * reselect for all MSTIs so that updtRolesTree is called * for each MSTI and due to above clause g) MSTI role is * changed to Master or reflects CIST port role. * Because in 802.1Q-2005 this will not happen when BPDU arrives * at boundary port - the rcvdMsg is not set for the MSTIs and * updtRolesTree is not called. * Bad IEEE !!! */ if(cist && (ptp->selectedRole != ptp->role)) reselectMSTIs(prt); continue; } } } } /* 13.27 The Port Timers state machine */ static void PTSM_tick(port_t *prt) { per_tree_port_t *ptp; if(prt->helloWhen) --(prt->helloWhen); if(prt->mdelayWhile) --(prt->mdelayWhile); if(prt->edgeDelayWhile) --(prt->edgeDelayWhile); if(prt->txCount) --(prt->txCount); if(prt->brAssuRcvdInfoWhile) --(prt->brAssuRcvdInfoWhile); FOREACH_PTP_IN_PORT(ptp, prt) { if(ptp->fdWhile) --(ptp->fdWhile); if(ptp->rrWhile) --(ptp->rrWhile); if(ptp->rbWhile) --(ptp->rbWhile); if(ptp->tcWhile) { if(0 == --(ptp->tcWhile)) set_TopologyChange(ptp->tree, false, prt); } if(ptp->rcvdInfoWhile) --(ptp->rcvdInfoWhile); } } /* 13.28 Port Receive state machine */ #define PRSM_begin(prt) PRSM_to_DISCARD((prt), false) static bool PRSM_to_DISCARD(port_t *prt, bool dry_run) { if(dry_run) { return (prt->PRSM_state != PRSM_DISCARD) || prt->rcvdBpdu || prt->rcvdRSTP || prt->rcvdSTP || (prt->edgeDelayWhile != prt->bridge->Migrate_Time) || clearAllRcvdMsgs(prt, dry_run); } prt->PRSM_state = PRSM_DISCARD; prt->rcvdBpdu = false; prt->rcvdRSTP = false; prt->rcvdSTP = false; clearAllRcvdMsgs(prt, false /* actual run */); assign(prt->edgeDelayWhile, prt->bridge->Migrate_Time); /* No need to run, no one condition will be met * if(!begin) * PRSM_run(prt, false); */ return false; } static void PRSM_to_RECEIVE(port_t *prt) { prt->PRSM_state = PRSM_RECEIVE; updtBPDUVersion(prt); prt->rcvdInternal = fromSameRegion(prt); setRcvdMsgs(prt); prt->operEdge = false; prt->rcvdBpdu = false; assign(prt->edgeDelayWhile, prt->bridge->Migrate_Time); /* No need to run, no one condition will be met PRSM_run(prt, false); */ } static bool PRSM_run(port_t *prt, bool dry_run) { per_tree_port_t *ptp; bool rcvdAnyMsg; if((prt->rcvdBpdu || (prt->edgeDelayWhile != prt->bridge->Migrate_Time)) && !prt->portEnabled) { return PRSM_to_DISCARD(prt, dry_run); } switch(prt->PRSM_state) { case PRSM_DISCARD: if(prt->rcvdBpdu && prt->portEnabled) { if(dry_run) /* state change */ return true; PRSM_to_RECEIVE(prt); } return false; case PRSM_RECEIVE: rcvdAnyMsg = false; FOREACH_PTP_IN_PORT(ptp, prt) { if(ptp->rcvdMsg) { rcvdAnyMsg = true; break; } } if(prt->rcvdBpdu && prt->portEnabled && !rcvdAnyMsg) { if(dry_run) /* at least rcvdBpdu will change */ return true; PRSM_to_RECEIVE(prt); } default: return false; } } /* 13.29 Port Protocol Migration state machine */ static bool PPMSM_run(port_t *prt, bool dry_run); #define PPMSM_begin(prt) PPMSM_to_CHECKING_RSTP(prt) static void PPMSM_to_CHECKING_RSTP(port_t *prt/*, bool begin*/) { prt->PPMSM_state = PPMSM_CHECKING_RSTP; bridge_t *br = prt->bridge; prt->mcheck = false; prt->sendRSTP = rstpVersion(br); assign(prt->mdelayWhile, br->Migrate_Time); /* No need to run, no one condition will be met * if(!begin) * PPMSM_run(prt, false); */ } static void PPMSM_to_SELECTING_STP(port_t *prt) { prt->PPMSM_state = PPMSM_SELECTING_STP; prt->sendRSTP = false; assign(prt->mdelayWhile, prt->bridge->Migrate_Time); PPMSM_run(prt, false /* actual run */); } static void PPMSM_to_SENSING(port_t *prt) { prt->PPMSM_state = PPMSM_SENSING; prt->rcvdRSTP = false; prt->rcvdSTP = false; PPMSM_run(prt, false /* actual run */); } static bool PPMSM_run(port_t *prt, bool dry_run) { bridge_t *br = prt->bridge; switch(prt->PPMSM_state) { case PPMSM_CHECKING_RSTP: if((prt->mdelayWhile != br->Migrate_Time) && !prt->portEnabled) { if(dry_run) /* at least mdelayWhile will change */ return true; PPMSM_to_CHECKING_RSTP(prt); return false; } if(0 == prt->mdelayWhile) { if(dry_run) /* state change */ return true; PPMSM_to_SENSING(prt); } return false; case PPMSM_SELECTING_STP: if(0 == prt->mdelayWhile || !prt->portEnabled || prt->mcheck) { if(dry_run) /* state change */ return true; PPMSM_to_SENSING(prt); } return false; case PPMSM_SENSING: if(!prt->portEnabled || prt->mcheck || (rstpVersion(br) && !prt->sendRSTP && prt->rcvdRSTP)) { if(dry_run) /* state change */ return true; PPMSM_to_CHECKING_RSTP(prt); return false; } if(prt->sendRSTP && prt->rcvdSTP) { if(dry_run) /* state change */ return true; PPMSM_to_SELECTING_STP(prt); } return false; } return false; } /* 13.30 Bridge Detection state machine */ static void BDSM_to_EDGE(port_t *prt/*, bool begin*/) { prt->BDSM_state = BDSM_EDGE; prt->operEdge = true; /* No need to run, no one condition will be met * if(!begin) * BDSM_run(prt, false); */ } static void BDSM_to_NOT_EDGE(port_t *prt/*, bool begin*/) { prt->BDSM_state = BDSM_NOT_EDGE; prt->operEdge = false; /* No need to run, no one condition will be met * if(!begin) * BDSM_run(prt, false); */ } static void BDSM_begin(port_t *prt/*, bool begin*/) { if(prt->AdminEdgePort) BDSM_to_EDGE(prt/*, begin*/); else BDSM_to_NOT_EDGE(prt/*, begin*/); } static bool BDSM_run(port_t *prt, bool dry_run) { per_tree_port_t *cist; switch(prt->BDSM_state) { case BDSM_EDGE: if(((!prt->portEnabled || !prt->AutoEdge) && !prt->AdminEdgePort) || !prt->operEdge ) { if(dry_run) /* state change */ return true; BDSM_to_NOT_EDGE(prt); } return false; case BDSM_NOT_EDGE: cist = GET_CIST_PTP_FROM_PORT(prt); /* NOTE: 802.1Q-2005(-2011) is not clear, which of the per-tree * "proposing" flags to use here, or one should combine * them all for all trees? * So, I decide that it will be the "proposing" flag * from CIST tree - it seems like a good bet. */ if((!prt->portEnabled && prt->AdminEdgePort) || ((0 == prt->edgeDelayWhile) && prt->AutoEdge && prt->sendRSTP && cist->proposing) ) { if(dry_run) /* state change */ return true; BDSM_to_EDGE(prt); } default: return false; } } /* 13.31 Port Transmit state machine */ static bool PTSM_run(port_t *prt, bool dry_run); #define PTSM_begin(prt) PTSM_to_TRANSMIT_INIT((prt), true, false) static bool PTSM_to_TRANSMIT_INIT(port_t *prt, bool begin, bool dry_run) { if(dry_run) { return (prt->PTSM_state != PTSM_TRANSMIT_INIT) || (!prt->newInfo) || (!prt->newInfoMsti) || (0 != prt->txCount); } prt->PTSM_state = PTSM_TRANSMIT_INIT; prt->newInfo = true; prt->newInfoMsti = true; assign(prt->txCount, 0u); if(!begin && prt->portEnabled) /* prevent infinite loop */ PTSM_run(prt, false /* actual run */); return false; } static void PTSM_to_TRANSMIT_CONFIG(port_t *prt) { prt->PTSM_state = PTSM_TRANSMIT_CONFIG; prt->newInfo = false; txConfig(prt); ++(prt->txCount); prt->tcAck = false; PTSM_run(prt, false /* actual run */); } static void PTSM_to_TRANSMIT_TCN(port_t *prt) { prt->PTSM_state = PTSM_TRANSMIT_TCN; prt->newInfo = false; txTcn(prt); ++(prt->txCount); PTSM_run(prt, false /* actual run */); } static void PTSM_to_TRANSMIT_RSTP(port_t *prt) { prt->PTSM_state = PTSM_TRANSMIT_RSTP; prt->newInfo = false; prt->newInfoMsti = false; txMstp(prt); ++(prt->txCount); prt->tcAck = false; PTSM_run(prt, false /* actual run */); } static void PTSM_to_TRANSMIT_PERIODIC(port_t *prt) { prt->PTSM_state = PTSM_TRANSMIT_PERIODIC; per_tree_port_t *ptp = GET_CIST_PTP_FROM_PORT(prt); bool cistDesignatedOrTCpropagatingRootPort = (roleDesignated == ptp->role) || ((roleRoot == ptp->role) && (0 != ptp->tcWhile)); bool mstiDesignatedOrTCpropagatingRootPort; mstiDesignatedOrTCpropagatingRootPort = false; list_for_each_entry_continue(ptp, &prt->trees, port_list) { if((roleDesignated == ptp->role) || ((roleRoot == ptp->role) && (0 != ptp->tcWhile)) ) { mstiDesignatedOrTCpropagatingRootPort = true; break; } } prt->newInfo = prt->newInfo || cistDesignatedOrTCpropagatingRootPort; prt->newInfoMsti = prt->newInfoMsti || mstiDesignatedOrTCpropagatingRootPort; PTSM_run(prt, false /* actual run */); } static void PTSM_to_IDLE(port_t *prt) { prt->PTSM_state = PTSM_IDLE; per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); prt->helloWhen = cist->portTimes.Hello_Time; PTSM_run(prt, false /* actual run */); } static bool PTSM_run(port_t *prt, bool dry_run) { /* bool allTransmitReady; */ per_tree_port_t *ptp; port_role_t cistRole; bool mstiMasterPort; if(!prt->portEnabled) { return PTSM_to_TRANSMIT_INIT(prt, false, dry_run); } switch(prt->PTSM_state) { case PTSM_TRANSMIT_INIT: /* return; */ case PTSM_TRANSMIT_CONFIG: /* return; */ case PTSM_TRANSMIT_TCN: /* return; */ case PTSM_TRANSMIT_RSTP: /* return; */ case PTSM_TRANSMIT_PERIODIC: if(dry_run) /* state change */ return true; PTSM_to_IDLE(prt); /* UnConditional Transition */ return false; case PTSM_IDLE: /* allTransmitReady = true; */ ptp = GET_CIST_PTP_FROM_PORT(prt); if(!ptp->selected || ptp->updtInfo) { /* allTransmitReady = false; */ return false; } cistRole = ptp->role; mstiMasterPort = false; list_for_each_entry_continue(ptp, &prt->trees, port_list) { if(!ptp->selected || ptp->updtInfo) { /* allTransmitReady = false; */ return false; } if(roleMaster == ptp->role) mstiMasterPort = true; } if(0 == prt->helloWhen) { if(dry_run) /* state change */ return true; PTSM_to_TRANSMIT_PERIODIC(prt); return false; } if(!(prt->txCount < prt->bridge->Transmit_Hold_Count)) return false; if(prt->bpduFilterPort) return false; if(prt->sendRSTP) { /* implement MSTP */ if(prt->newInfo || (prt->newInfoMsti && !mstiMasterPort) || assurancePort(prt) ) { if(dry_run) /* state change */ return true; PTSM_to_TRANSMIT_RSTP(prt); return false; } } else { /* fallback to STP */ if(prt->newInfo && (roleDesignated == cistRole)) { if(dry_run) /* state change */ return true; PTSM_to_TRANSMIT_CONFIG(prt); return false; } if(prt->newInfo && (roleRoot == cistRole)) { if(dry_run) /* state change */ return true; PTSM_to_TRANSMIT_TCN(prt); return false; } } return false; } return false; } /* 13.32 Port Information state machine */ #ifdef PISM_ENABLE_LOG #define PISM_LOG(_fmt, _args...) SMLOG_MSTINAME(ptp, _fmt, ##_args) #else #define PISM_LOG(_fmt, _args...) {} #endif /* PISM_ENABLE_LOG */ static bool PISM_run(per_tree_port_t *ptp, bool dry_run); #define PISM_begin(ptp) PISM_to_DISABLED((ptp), true) static void PISM_to_DISABLED(per_tree_port_t *ptp, bool begin) { PISM_LOG(""); ptp->PISM_state = PISM_DISABLED; ptp->rcvdMsg = false; ptp->proposing = false; ptp->proposed = false; ptp->agree = false; ptp->agreed = false; assign(ptp->rcvdInfoWhile, 0u); ptp->infoIs = ioDisabled; ptp->reselect = true; ptp->selected = false; if(!begin) PISM_run(ptp, false /* actual run */); } static void PISM_to_AGED(per_tree_port_t *ptp) { PISM_LOG(""); ptp->PISM_state = PISM_AGED; ptp->infoIs = ioAged; ptp->reselect = true; ptp->selected = false; PISM_run(ptp, false /* actual run */); } static void PISM_to_UPDATE(per_tree_port_t *ptp) { PISM_LOG(""); ptp->PISM_state = PISM_UPDATE; ptp->proposing = false; ptp->proposed = false; ptp->agreed = ptp->agreed && betterorsameInfo(ptp, ioMine); ptp->synced = ptp->synced && ptp->agreed; assign(ptp->portPriority, ptp->designatedPriority); assign(ptp->portTimes, ptp->designatedTimes); ptp->updtInfo = false; ptp->infoIs = ioMine; /* newInfoXst = TRUE; */ port_t *prt = ptp->port; if(0 == ptp->MSTID) prt->newInfo = true; else prt->newInfoMsti = true; PISM_run(ptp, false /* actual run */); } static void PISM_to_SUPERIOR_DESIGNATED(per_tree_port_t *ptp) { PISM_LOG(""); ptp->PISM_state = PISM_SUPERIOR_DESIGNATED; port_t *prt = ptp->port; prt->infoInternal = prt->rcvdInternal; ptp->agreed = false; ptp->proposing = false; recordProposal(ptp); setTcFlags(ptp); ptp->agree = ptp->agree && betterorsameInfo(ptp, ioReceived); recordAgreement(ptp); ptp->synced = ptp->synced && ptp->agreed; recordPriority(ptp); recordTimes(ptp); updtRcvdInfoWhile(ptp); ptp->infoIs = ioReceived; ptp->reselect = true; ptp->selected = false; ptp->rcvdMsg = false; PISM_run(ptp, false /* actual run */); } static void PISM_to_REPEATED_DESIGNATED(per_tree_port_t *ptp) { PISM_LOG(""); ptp->PISM_state = PISM_REPEATED_DESIGNATED; port_t *prt = ptp->port; prt->infoInternal = prt->rcvdInternal; recordProposal(ptp); setTcFlags(ptp); recordAgreement(ptp); updtRcvdInfoWhile(ptp); ptp->rcvdMsg = false; PISM_run(ptp, false /* actual run */); } static void PISM_to_INFERIOR_DESIGNATED(per_tree_port_t *ptp) { PISM_LOG(""); ptp->PISM_state = PISM_INFERIOR_DESIGNATED; recordDispute(ptp); ptp->rcvdMsg = false; PISM_run(ptp, false /* actual run */); } static void PISM_to_NOT_DESIGNATED(per_tree_port_t *ptp) { PISM_LOG(""); ptp->PISM_state = PISM_NOT_DESIGNATED; recordAgreement(ptp); setTcFlags(ptp); ptp->rcvdMsg = false; PISM_run(ptp, false /* actual run */); } static void PISM_to_OTHER(per_tree_port_t *ptp) { PISM_LOG(""); ptp->PISM_state = PISM_OTHER; ptp->rcvdMsg = false; PISM_run(ptp, false /* actual run */); } static void PISM_to_CURRENT(per_tree_port_t *ptp) { PISM_LOG(""); ptp->PISM_state = PISM_CURRENT; PISM_run(ptp, false /* actual run */); } static void PISM_to_RECEIVE(per_tree_port_t *ptp) { PISM_LOG(""); ptp->PISM_state = PISM_RECEIVE; ptp->rcvdInfo = rcvInfo(ptp); recordMastered(ptp); PISM_run(ptp, false /* actual run */); } static bool PISM_run(per_tree_port_t *ptp, bool dry_run) { bool rcvdXstMsg, updtXstInfo; port_t *prt = ptp->port; if((!prt->portEnabled) && (ioDisabled != ptp->infoIs)) { if(dry_run) /* at least infoIs will change */ return true; PISM_to_DISABLED(ptp, false); return false; } switch(ptp->PISM_state) { case PISM_DISABLED: if(prt->portEnabled) { if(dry_run) /* state change */ return true; PISM_to_AGED(ptp); return false; } if(ptp->rcvdMsg) { if(dry_run) /* at least rcvdMsg will change */ return true; PISM_to_DISABLED(ptp, false); } return false; case PISM_AGED: if(ptp->selected && ptp->updtInfo) { if(dry_run) /* state change */ return true; PISM_to_UPDATE(ptp); } return false; case PISM_UPDATE: /* return; */ case PISM_SUPERIOR_DESIGNATED: /* return; */ case PISM_REPEATED_DESIGNATED: /* return; */ case PISM_INFERIOR_DESIGNATED: /* return; */ case PISM_NOT_DESIGNATED: /* return; */ case PISM_OTHER: if(dry_run) /* state change */ return true; PISM_to_CURRENT(ptp); return false; case PISM_CURRENT: /* * Although 802.1Q-2005 does not define rcvdXstMsg and updtXstInfo * from 802.1s we can conclude that they are: * - rcvdXstMsg = rcvdCistMsg, if tree is CIST * rcvdMstiMsg, if tree is MSTI. * - updtXstInfo = updtCistInfo, if tree is CIST * updtMstiInfo, if tree is MSTI. */ if(0 == ptp->MSTID) { /* CIST */ rcvdXstMsg = ptp->rcvdMsg; /* 13.25.12 */ updtXstInfo = ptp->updtInfo; /* 13.25.16 */ } else { /* MSTI */ per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); rcvdXstMsg = !cist->rcvdMsg && ptp->rcvdMsg; /* 13.25.13 */ updtXstInfo = ptp->updtInfo || cist->updtInfo; /* 13.25.17 */ } if(rcvdXstMsg && !updtXstInfo) { if(dry_run) /* state change */ return true; PISM_to_RECEIVE(ptp); return false; } if((ioReceived == ptp->infoIs) && (0 == ptp->rcvdInfoWhile) && !ptp->updtInfo && !rcvdXstMsg) { if(dry_run) /* state change */ return true; PISM_to_AGED(ptp); return false; } if(ptp->selected && ptp->updtInfo) { if(dry_run) /* state change */ return true; PISM_to_UPDATE(ptp); } return false; case PISM_RECEIVE: switch(ptp->rcvdInfo) { case SuperiorDesignatedInfo: if(dry_run) /* state change */ return true; PISM_to_SUPERIOR_DESIGNATED(ptp); return false; case RepeatedDesignatedInfo: if(dry_run) /* state change */ return true; PISM_to_REPEATED_DESIGNATED(ptp); return false; case InferiorDesignatedInfo: if(dry_run) /* state change */ return true; PISM_to_INFERIOR_DESIGNATED(ptp); return false; case InferiorRootAlternateInfo: if(dry_run) /* state change */ return true; PISM_to_NOT_DESIGNATED(ptp); return false; case OtherInfo: if(dry_run) /* state change */ return true; PISM_to_OTHER(ptp); return false; } return false; } return false; } /* 13.33 Port Role Selection state machine */ static bool PRSSM_run(tree_t *tree, bool dry_run); #define PRSSM_begin(tree) PRSSM_to_INIT_TREE(tree) static void PRSSM_to_INIT_TREE(tree_t *tree/*, bool begin*/) { tree->PRSSM_state = PRSSM_INIT_TREE; updtRolesDisabledTree(tree); /* No need to check, as we assume begin = true here * because transition to this state can be initiated only by BEGIN var. * In other words, this function is called via xxx_begin macro only. * if(!begin) * PRSSM_run(prt, false); */ } static void PRSSM_to_ROLE_SELECTION(tree_t *tree) { tree->PRSSM_state = PRSSM_ROLE_SELECTION; clearReselectTree(tree); updtRolesTree(tree); setSelectedTree(tree); /* No need to run, no one condition will be met PRSSM_run(tree, false); */ } static bool PRSSM_run(tree_t *tree, bool dry_run) { per_tree_port_t *ptp; switch(tree->PRSSM_state) { case PRSSM_INIT_TREE: if(dry_run) /* state change */ return true; PRSSM_to_ROLE_SELECTION(tree); return false; case PRSSM_ROLE_SELECTION: FOREACH_PTP_IN_TREE(ptp, tree) if(ptp->reselect) { if(dry_run) /* at least reselect will change */ return true; PRSSM_to_ROLE_SELECTION(tree); return false; } return false; } return false; } /* 13.34 Port Role Transitions state machine */ #ifdef PRTSM_ENABLE_LOG #define PRTSM_LOG(_fmt, _args...) SMLOG_MSTINAME(ptp, _fmt, ##_args) #else #define PRTSM_LOG(_fmt, _args...) {} #endif /* PRTSM_ENABLE_LOG */ static bool PRTSM_runr(per_tree_port_t *ptp, bool recursive_call, bool dry_run); #define PRTSM_run(ptp, dry_run) PRTSM_runr((ptp), false, (dry_run)) #define PRTSM_begin(ptp) PRTSM_to_INIT_PORT(ptp) /* Disabled Port role transitions */ static void PRTSM_to_INIT_PORT(per_tree_port_t *ptp/*, bool begin*/) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_INIT_PORT; unsigned int MaxAge, FwdDelay; per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(ptp->port); ptp->role = roleDisabled; ptp->learn = false; ptp->forward = false; ptp->synced = false; ptp->sync = true; ptp->reRoot = true; /* 13.25.6 */ FwdDelay = cist->designatedTimes.Forward_Delay; assign(ptp->rrWhile, FwdDelay); /* 13.25.8 */ MaxAge = cist->designatedTimes.Max_Age; assign(ptp->fdWhile, MaxAge); assign(ptp->rbWhile, 0u); /* No need to check, as we assume begin = true here * because transition to this state can be initiated only by BEGIN var. * In other words, this function is called via xxx_begin macro only. * if(!begin) * PRTSM_runr(ptp, false, false); */ } static void PRTSM_to_DISABLE_PORT(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_DISABLE_PORT; /* Although 802.1Q-2005 says here to do role = selectedRole * I have difficulties with it in the next scenario: * 1) port was designated (role == selectedRole == roleDesignated) * 2) some config event occurs, e.g. MAC address changes and * br_state_machines_begin is called * 3) role == selectedRole == roleDisabled, PRTSM_state = PRTSM_INIT_PORT * Because port was not actually down, on the next run * Port Role Selection state machine sets selectedRole = roleDesignated * and updtInfo = true: * 4) we have unconditional transition to DISABLE_PORT, and because * updtInfo = true we can not follow transition to DESIGNATED_PORT * 5) if we follow standard, role = selectedRole = roleDesignated and * on the next run we have transition to the DISABLED_PORT * And there we stuck. role == selectedRole, so we can not transit to * DESIGNATED_PORT (it requires role != selectedRole ). * * Solution: do not follow the standard, and do role = roleDisabled * instead of role = selectedRole. */ ptp->role = roleDisabled; ptp->learn = false; ptp->forward = false; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_DISABLED_PORT(per_tree_port_t *ptp, unsigned int MaxAge) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_DISABLED_PORT; assign(ptp->fdWhile, MaxAge); ptp->synced = true; assign(ptp->rrWhile, 0u); ptp->sync = false; ptp->reRoot = false; PRTSM_runr(ptp, true, false /* actual run */); } /* MasterPort role transitions */ static void PRTSM_to_MASTER_PROPOSED(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_MASTER_PROPOSED; setSyncTree(ptp->tree); ptp->proposed = false; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_MASTER_AGREED(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_MASTER_AGREED; ptp->proposed = false; ptp->sync = false; ptp->agree = true; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_MASTER_SYNCED(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_MASTER_SYNCED; assign(ptp->rrWhile, 0u); ptp->synced = true; ptp->sync = false; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_MASTER_RETIRED(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_MASTER_RETIRED; ptp->reRoot = false; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_MASTER_FORWARD(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_MASTER_FORWARD; ptp->forward = true; assign(ptp->fdWhile, 0u); ptp->agreed = ptp->port->sendRSTP; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_MASTER_LEARN(per_tree_port_t *ptp, unsigned int forwardDelay) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_MASTER_LEARN; ptp->learn = true; assign(ptp->fdWhile, forwardDelay); PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_MASTER_DISCARD(per_tree_port_t *ptp, unsigned int forwardDelay) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_MASTER_DISCARD; ptp->learn = false; ptp->forward = false; ptp->disputed = false; assign(ptp->fdWhile, forwardDelay); PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_MASTER_PORT(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_MASTER_PORT; ptp->role = roleMaster; PRTSM_runr(ptp, true, false /* actual run */); } /* RootPort role transitions */ static void PRTSM_to_ROOT_PROPOSED(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_ROOT_PROPOSED; setSyncTree(ptp->tree); ptp->proposed = false; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_ROOT_AGREED(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_ROOT_AGREED; ptp->proposed = false; ptp->sync = false; ptp->agree = true; /* newInfoXst = TRUE; */ port_t *prt = ptp->port; if(0 == ptp->MSTID) prt->newInfo = true; else prt->newInfoMsti = true; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_ROOT_SYNCED(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_ROOT_SYNCED; ptp->synced = true; ptp->sync = false; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_REROOT(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_REROOT; setReRootTree(ptp->tree); PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_ROOT_FORWARD(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_ROOT_FORWARD; assign(ptp->fdWhile, 0u); ptp->forward = true; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_ROOT_LEARN(per_tree_port_t *ptp, unsigned int forwardDelay) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_ROOT_LEARN; assign(ptp->fdWhile, forwardDelay); ptp->learn = true; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_REROOTED(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_REROOTED; ptp->reRoot = false; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_ROOT_PORT(per_tree_port_t *ptp, unsigned int FwdDelay) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_ROOT_PORT; ptp->role = roleRoot; assign(ptp->rrWhile, FwdDelay); PRTSM_runr(ptp, true, false /* actual run */); } /* DesignatedPort role transitions */ static void PRTSM_to_DESIGNATED_PROPOSE(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_DESIGNATED_PROPOSE; port_t *prt = ptp->port; ptp->proposing = true; /* newInfoXst = TRUE; */ if(0 == ptp->MSTID) { /* CIST */ /* 13.25.8. This tree is CIST. */ unsigned int MaxAge = ptp->designatedTimes.Max_Age; /* 13.25.c) -> 17.20.4 of 802.1D : EdgeDelay */ unsigned int EdgeDelay = prt->operPointToPointMAC ? prt->bridge->Migrate_Time : MaxAge; assign(prt->edgeDelayWhile, EdgeDelay); prt->newInfo = true; } else prt->newInfoMsti = true; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_DESIGNATED_AGREED(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_DESIGNATED_AGREED; ptp->proposed = false; ptp->sync = false; ptp->agree = true; /* newInfoXst = TRUE; */ port_t *prt = ptp->port; if(0 == ptp->MSTID) prt->newInfo = true; else prt->newInfoMsti = true; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_DESIGNATED_SYNCED(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_DESIGNATED_SYNCED; assign(ptp->rrWhile, 0u); ptp->synced = true; ptp->sync = false; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_DESIGNATED_RETIRED(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_DESIGNATED_RETIRED; ptp->reRoot = false; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_DESIGNATED_FORWARD(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_DESIGNATED_FORWARD; ptp->forward = true; assign(ptp->fdWhile, 0u); ptp->agreed = ptp->port->sendRSTP; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_DESIGNATED_LEARN(per_tree_port_t *ptp, unsigned int forwardDelay) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_DESIGNATED_LEARN; ptp->learn = true; assign(ptp->fdWhile, forwardDelay); PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_DESIGNATED_DISCARD(per_tree_port_t *ptp, unsigned int forwardDelay) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_DESIGNATED_DISCARD; ptp->learn = false; ptp->forward = false; ptp->disputed = false; assign(ptp->fdWhile, forwardDelay); PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_DESIGNATED_PORT(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_DESIGNATED_PORT; ptp->role = roleDesignated; PRTSM_runr(ptp, true, false /* actual run */); } /* AlternatePort and BackupPort role transitions */ static void PRTSM_to_BLOCK_PORT(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_BLOCK_PORT; ptp->role = ptp->selectedRole; ptp->learn = false; ptp->forward = false; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_BACKUP_PORT(per_tree_port_t *ptp, unsigned int HelloTime) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_BACKUP_PORT; assign(ptp->rbWhile, 2 * HelloTime); PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_ALTERNATE_PROPOSED(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_ALTERNATE_PROPOSED; setSyncTree(ptp->tree); ptp->proposed = false; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_ALTERNATE_AGREED(per_tree_port_t *ptp) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_ALTERNATE_AGREED; ptp->proposed = false; ptp->agree = true; /* newInfoXst = TRUE; */ port_t *prt = ptp->port; if(0 == ptp->MSTID) prt->newInfo = true; else prt->newInfoMsti = true; PRTSM_runr(ptp, true, false /* actual run */); } static void PRTSM_to_ALTERNATE_PORT(per_tree_port_t *ptp, unsigned int forwardDelay) { PRTSM_LOG(""); ptp->PRTSM_state = PRTSM_ALTERNATE_PORT; assign(ptp->fdWhile, forwardDelay); ptp->synced = true; assign(ptp->rrWhile, 0u); ptp->sync = false; ptp->reRoot = false; PRTSM_runr(ptp, true, false /* actual run */); } static bool PRTSM_runr(per_tree_port_t *ptp, bool recursive_call, bool dry_run) { /* Following vars do not need recalculating on recursive calls */ static unsigned int MaxAge, FwdDelay, forwardDelay, HelloTime; static port_t *prt; static tree_t *tree; static per_tree_port_t *cist; /* Following vars are recalculated on each state transition */ bool allSynced, reRooted; /* Following vars are auxiliary and don't depend on recursive_call */ per_tree_port_t *ptp_1; if(!recursive_call) { /* calculate these intermediate vars only first time in chain of * recursive calls */ prt = ptp->port; tree = ptp->tree; cist = GET_CIST_PTP_FROM_PORT(prt); /* 13.25.6 */ FwdDelay = cist->designatedTimes.Forward_Delay; /* 13.25.7 */ HelloTime = cist->portTimes.Hello_Time; /* 13.25.d) -> 17.20.5 of 802.1D */ forwardDelay = prt->sendRSTP ? HelloTime : FwdDelay; /* 13.25.8 */ MaxAge = cist->designatedTimes.Max_Age; } PRTSM_LOG("role = %d, selectedRole = %d, selected = %d, updtInfo = %d", ptp->role, ptp->selectedRole, ptp->selected, ptp->updtInfo); if((ptp->role != ptp->selectedRole) && ptp->selected && !ptp->updtInfo) { switch(ptp->selectedRole) { case roleDisabled: if(dry_run) /* at least role will change */ return true; PRTSM_to_DISABLE_PORT(ptp); return false; case roleMaster: if(dry_run) /* at least role will change */ return true; PRTSM_to_MASTER_PORT(ptp); return false; case roleRoot: if(dry_run) /* at least role will change */ return true; PRTSM_to_ROOT_PORT(ptp, FwdDelay); return false; case roleDesignated: if(dry_run) /* at least role will change */ return true; PRTSM_to_DESIGNATED_PORT(ptp); return false; case roleAlternate: case roleBackup: if(dry_run) /* at least role will change */ return true; PRTSM_to_BLOCK_PORT(ptp); return false; } } /* 13.25.1 */ allSynced = true; FOREACH_PTP_IN_TREE(ptp_1, tree) { /* a) */ if(!ptp_1->selected || (ptp_1->role != ptp_1->selectedRole) || ptp_1->updtInfo ) { allSynced = false; break; } /* b) */ switch(ptp->role) { case roleRoot: case roleAlternate: if((roleRoot != ptp_1->role) && !ptp_1->synced) allSynced = false; break; case roleDesignated: case roleMaster: if((ptp != ptp_1) && !ptp_1->synced) allSynced = false; break; default: allSynced = false; } if(!allSynced) break; } switch(ptp->PRTSM_state) { /* Disabled Port role transitions */ case PRTSM_INIT_PORT: if(dry_run) /* state change */ return true; PRTSM_to_DISABLE_PORT(ptp); return false; case PRTSM_DISABLE_PORT: if(ptp->selected && !ptp->updtInfo && !ptp->learning && !ptp->forwarding ) { if(dry_run) /* state change */ return true; PRTSM_to_DISABLED_PORT(ptp, MaxAge); } return false; case PRTSM_DISABLED_PORT: if(ptp->selected && !ptp->updtInfo && (ptp->sync || ptp->reRoot || !ptp->synced || (ptp->fdWhile != MaxAge)) ) { if(dry_run) /* one of (sync,reRoot,synced,fdWhile) will change */ return true; PRTSM_to_DISABLED_PORT(ptp, MaxAge); } return false; /* MasterPort role transitions */ case PRTSM_MASTER_PROPOSED: /* return; */ case PRTSM_MASTER_AGREED: /* return; */ case PRTSM_MASTER_SYNCED: /* return; */ case PRTSM_MASTER_RETIRED: /* return; */ case PRTSM_MASTER_FORWARD: /* return; */ case PRTSM_MASTER_LEARN: /* return; */ case PRTSM_MASTER_DISCARD: if(dry_run) /* state change */ return true; PRTSM_to_MASTER_PORT(ptp); return false; case PRTSM_MASTER_PORT: if(!(ptp->selected && !ptp->updtInfo)) return false; if(ptp->reRoot && (0 == ptp->rrWhile)) { if(dry_run) /* state change */ return true; PRTSM_to_MASTER_RETIRED(ptp); return false; } if((!ptp->learning && !ptp->forwarding && !ptp->synced) || (ptp->agreed && !ptp->synced) || (prt->operEdge && !ptp->synced) || (ptp->sync && ptp->synced) ) { if(dry_run) /* state change */ return true; PRTSM_to_MASTER_SYNCED(ptp); return false; } if((allSynced && !ptp->agree) || (ptp->proposed && ptp->agree) ) { if(dry_run) /* state change */ return true; PRTSM_to_MASTER_AGREED(ptp); return false; } if(ptp->proposed && !ptp->agree) { if(dry_run) /* state change */ return true; PRTSM_to_MASTER_PROPOSED(ptp); return false; } if(((0 == ptp->fdWhile) || allSynced) && ptp->learn && !ptp->forward ) { if(dry_run) /* state change */ return true; PRTSM_to_MASTER_FORWARD(ptp); return false; } if(((0 == ptp->fdWhile) || allSynced) && !ptp->learn ) { if(dry_run) /* state change */ return true; PRTSM_to_MASTER_LEARN(ptp, forwardDelay); return false; } if(((ptp->sync && !ptp->synced) || (ptp->reRoot && (0 != ptp->rrWhile)) || ptp->disputed ) && !prt->operEdge && (ptp->learn || ptp->forward) ) { if(dry_run) /* state change */ return true; PRTSM_to_MASTER_DISCARD(ptp, forwardDelay); return false; } return false; /* RootPort role transitions */ case PRTSM_ROOT_PROPOSED: /* return; */ case PRTSM_ROOT_AGREED: /* return; */ case PRTSM_ROOT_SYNCED: /* return; */ case PRTSM_REROOT: /* return; */ case PRTSM_ROOT_FORWARD: /* return; */ case PRTSM_ROOT_LEARN: /* return; */ case PRTSM_REROOTED: if(dry_run) /* state change */ return true; PRTSM_to_ROOT_PORT(ptp, FwdDelay); return false; case PRTSM_ROOT_PORT: if(!(ptp->selected && !ptp->updtInfo)) return false; if(!ptp->forward && !ptp->reRoot) { if(dry_run) /* state change */ return true; PRTSM_to_REROOT(ptp); return false; } if((ptp->agreed && !ptp->synced) || (ptp->sync && ptp->synced)) { if(dry_run) /* state change */ return true; PRTSM_to_ROOT_SYNCED(ptp); return false; } if((allSynced && !ptp->agree) || (ptp->proposed && ptp->agree)) { if(dry_run) /* state change */ return true; PRTSM_to_ROOT_AGREED(ptp); return false; } if(ptp->proposed && !ptp->agree) { if(dry_run) /* state change */ return true; PRTSM_to_ROOT_PROPOSED(ptp); return false; } /* 17.20.10 of 802.1D : reRooted */ reRooted = true; FOREACH_PTP_IN_TREE(ptp_1, tree) { if((ptp != ptp_1) && (0 != ptp_1->rrWhile)) { reRooted = false; break; } } if((0 == ptp->fdWhile) || (reRooted && (0 == ptp->rbWhile) && rstpVersion(prt->bridge)) ) { if(!ptp->learn) { if(dry_run) /* state change */ return true; PRTSM_to_ROOT_LEARN(ptp, forwardDelay); return false; } else if(!ptp->forward) { if(dry_run) /* state change */ return true; PRTSM_to_ROOT_FORWARD(ptp); return false; } } if(ptp->reRoot && ptp->forward) { if(dry_run) /* state change */ return true; PRTSM_to_REROOTED(ptp); return false; } if(ptp->rrWhile != FwdDelay) { if(dry_run) /* state change */ return true; PRTSM_to_ROOT_PORT(ptp, FwdDelay); return false; } return false; /* DesignatedPort role transitions */ case PRTSM_DESIGNATED_PROPOSE: /* return; */ case PRTSM_DESIGNATED_AGREED: /* return; */ case PRTSM_DESIGNATED_SYNCED: /* return; */ case PRTSM_DESIGNATED_RETIRED: /* return; */ case PRTSM_DESIGNATED_FORWARD: /* return; */ case PRTSM_DESIGNATED_LEARN: /* return; */ case PRTSM_DESIGNATED_DISCARD: if(dry_run) /* state change */ return true; PRTSM_to_DESIGNATED_PORT(ptp); return false; case PRTSM_DESIGNATED_PORT: if(!(ptp->selected && !ptp->updtInfo)) return false; if(ptp->reRoot && (0 == ptp->rrWhile)) { if(dry_run) /* state change */ return true; PRTSM_to_DESIGNATED_RETIRED(ptp); return false; } if((!ptp->learning && !ptp->forwarding && !ptp->synced) || (ptp->agreed && !ptp->synced) || (prt->operEdge && !ptp->synced) || (ptp->sync && ptp->synced) ) { if(dry_run) /* state change */ return true; PRTSM_to_DESIGNATED_SYNCED(ptp); return false; } if(allSynced && (ptp->proposed || !ptp->agree)) { if(dry_run) /* state change */ return true; PRTSM_to_DESIGNATED_AGREED(ptp); return false; } if(!ptp->forward && !ptp->agreed && !ptp->proposing && !prt->operEdge) { if(dry_run) /* state change */ return true; PRTSM_to_DESIGNATED_PROPOSE(ptp); return false; } /* Dont transition to learn/forward when BA inconsistent */ if(((0 == ptp->fdWhile) || ptp->agreed || prt->operEdge) && ((0 == ptp->rrWhile) || !ptp->reRoot) && !ptp->sync && !ptp->port->BaInconsistent ) { if(!ptp->learn) { if(dry_run) /* state change */ return true; PRTSM_to_DESIGNATED_LEARN(ptp, forwardDelay); return false; } else if(!ptp->forward) { if(dry_run) /* state change */ return true; PRTSM_to_DESIGNATED_FORWARD(ptp); return false; } } /* Transition to discarding when BA inconsistent */ if(((ptp->sync && !ptp->synced) || (ptp->reRoot && (0 != ptp->rrWhile)) || ptp->disputed || ptp->port->BaInconsistent ) && !prt->operEdge && (ptp->learn || ptp->forward) ) { if(dry_run) /* state change */ return true; PRTSM_to_DESIGNATED_DISCARD(ptp, forwardDelay); return false; } return false; /* AlternatePort and BackupPort role transitions */ case PRTSM_BLOCK_PORT: if(ptp->selected && !ptp->updtInfo && !ptp->learning && !ptp->forwarding ) { if(dry_run) /* state change */ return true; PRTSM_to_ALTERNATE_PORT(ptp, forwardDelay); } return false; case PRTSM_BACKUP_PORT: /* return; */ case PRTSM_ALTERNATE_PROPOSED: /* return; */ case PRTSM_ALTERNATE_AGREED: if(dry_run) /* state change */ return true; PRTSM_to_ALTERNATE_PORT(ptp, forwardDelay); return false; case PRTSM_ALTERNATE_PORT: if(!(ptp->selected && !ptp->updtInfo)) return false; if((allSynced && !ptp->agree) || (ptp->proposed && ptp->agree)) { if(dry_run) /* state change */ return true; PRTSM_to_ALTERNATE_AGREED(ptp); return false; } if(ptp->proposed && !ptp->agree) { if(dry_run) /* state change */ return true; PRTSM_to_ALTERNATE_PROPOSED(ptp); return false; } if((ptp->rbWhile != 2 * HelloTime) && (roleBackup == ptp->role)) { if(dry_run) /* state change */ return true; PRTSM_to_BACKUP_PORT(ptp, HelloTime); return false; } if((ptp->fdWhile != forwardDelay) || ptp->sync || ptp->reRoot || !ptp->synced) { if(dry_run) /* state change */ return true; PRTSM_to_ALTERNATE_PORT(ptp, forwardDelay); return false; } return false; } return false; } /* 13.35 Port State Transition state machine */ static bool PSTSM_run(per_tree_port_t *ptp, bool dry_run); #define PSTSM_begin(ptp) PSTSM_to_DISCARDING((ptp), true) static void PSTSM_to_DISCARDING(per_tree_port_t *ptp, bool begin) { ptp->PSTSM_state = PSTSM_DISCARDING; /* This effectively sets BLOCKING state: disableLearning(); disableForwarding(); */ if(BR_STATE_BLOCKING != ptp->state) { if(!ptp->port->deleted) MSTP_OUT_set_state(ptp, BR_STATE_BLOCKING); } ptp->learning = false; ptp->forwarding = false; if(!begin) PSTSM_run(ptp, false /* actual run */); } static void PSTSM_to_LEARNING(per_tree_port_t *ptp) { ptp->PSTSM_state = PSTSM_LEARNING; /* enableLearning(); */ if(BR_STATE_LEARNING != ptp->state) { if(!ptp->port->deleted) MSTP_OUT_set_state(ptp, BR_STATE_LEARNING); } ptp->learning = true; PSTSM_run(ptp, false /* actual run */); } static void PSTSM_to_FORWARDING(per_tree_port_t *ptp) { ptp->PSTSM_state = PSTSM_FORWARDING; /* enableForwarding(); */ if(BR_STATE_FORWARDING != ptp->state) { if(!ptp->port->deleted) MSTP_OUT_set_state(ptp, BR_STATE_FORWARDING); } ptp->forwarding = true; /* No need to run, no one condition will be met PSTSM_run(ptp, false); */ } static bool PSTSM_run(per_tree_port_t *ptp, bool dry_run) { switch(ptp->PSTSM_state) { case PSTSM_DISCARDING: if(ptp->learn) { if(dry_run) /* state change */ return true; PSTSM_to_LEARNING(ptp); } return false; case PSTSM_LEARNING: if(!ptp->learn) { if(dry_run) /* state change */ return true; PSTSM_to_DISCARDING(ptp, false); } else if(ptp->forward) { if(dry_run) /* state change */ return true; PSTSM_to_FORWARDING(ptp); } return false; case PSTSM_FORWARDING: if(!ptp->forward) { if(dry_run) /* state change */ return true; PSTSM_to_DISCARDING(ptp, false); } return false; } return false; } /* 13.36 Topology Change state machine */ #define TCSM_begin(ptp) TCSM_to_INACTIVE((ptp), true) static void TCSM_to_INACTIVE(per_tree_port_t *ptp, bool begin) { ptp->TCSM_state = TCSM_INACTIVE; set_fdbFlush(ptp); assign(ptp->tcWhile, 0u); set_TopologyChange(ptp->tree, false, ptp->port); if(0 == ptp->MSTID) /* CIST */ ptp->port->tcAck = false; if(!begin) TCSM_run(ptp, false /* actual run */); } static bool TCSM_to_LEARNING(per_tree_port_t *ptp, bool dry_run) { if(dry_run) { if((ptp->TCSM_state != TCSM_LEARNING) || ptp->rcvdTc || ptp->tcProp) return true; if(0 == ptp->MSTID) /* CIST */ { port_t *prt = ptp->port; if(prt->rcvdTcn || prt->rcvdTcAck) return true; } return false; } ptp->TCSM_state = TCSM_LEARNING; if(0 == ptp->MSTID) /* CIST */ { port_t *prt = ptp->port; prt->rcvdTcn = false; prt->rcvdTcAck = false; } ptp->rcvdTc = false; ptp->tcProp = false; TCSM_run(ptp, false /* actual run */); return false; } static void TCSM_to_DETECTED(per_tree_port_t *ptp) { ptp->TCSM_state = TCSM_DETECTED; newTcWhile(ptp); setTcPropTree(ptp); /* newInfoXst = TRUE; */ port_t *prt = ptp->port; if(0 == ptp->MSTID) prt->newInfo = true; else prt->newInfoMsti = true; TCSM_run(ptp, false /* actual run */); } static void TCSM_to_NOTIFIED_TCN(per_tree_port_t *ptp) { ptp->TCSM_state = TCSM_NOTIFIED_TCN; newTcWhile(ptp); TCSM_run(ptp, false /* actual run */); } static void TCSM_to_NOTIFIED_TC(per_tree_port_t *ptp) { ptp->TCSM_state = TCSM_NOTIFIED_TC; ptp->rcvdTc = false; if(0 == ptp->MSTID) /* CIST */ { port_t *prt = ptp->port; prt->rcvdTcn = false; if(roleDesignated == ptp->role) prt->tcAck = true; } setTcPropTree(ptp); TCSM_run(ptp, false /* actual run */); } static void TCSM_to_PROPAGATING(per_tree_port_t *ptp) { ptp->TCSM_state = TCSM_PROPAGATING; newTcWhile(ptp); set_fdbFlush(ptp); ptp->tcProp = false; TCSM_run(ptp, false /* actual run */); } static void TCSM_to_ACKNOWLEDGED(per_tree_port_t *ptp) { ptp->TCSM_state = TCSM_ACKNOWLEDGED; assign(ptp->tcWhile, 0u); set_TopologyChange(ptp->tree, false, ptp->port); ptp->port->rcvdTcAck = false; TCSM_run(ptp, false /* actual run */); } static void TCSM_to_ACTIVE(per_tree_port_t *ptp) { ptp->TCSM_state = TCSM_ACTIVE; TCSM_run(ptp, false /* actual run */); } static bool TCSM_run(per_tree_port_t *ptp, bool dry_run) { bool active_port; port_t *prt = ptp->port; switch(ptp->TCSM_state) { case TCSM_INACTIVE: if(ptp->learn && !ptp->fdbFlush) { if(dry_run) /* state change */ return true; TCSM_to_LEARNING(ptp, false /* actual run */); } return false; case TCSM_LEARNING: active_port = (roleRoot == ptp->role) || (roleDesignated == ptp->role) || (roleMaster == ptp->role); if(active_port && ptp->forward && !prt->operEdge) { if(dry_run) /* state change */ return true; TCSM_to_DETECTED(ptp); return false; } if(ptp->rcvdTc || ptp->tcProp || ((0 == ptp->MSTID) && (prt->rcvdTcn || prt->rcvdTcAck))) { return TCSM_to_LEARNING(ptp, dry_run); } else if(!active_port && !(ptp->learn || ptp->learning)) { if(dry_run) /* state change */ return true; TCSM_to_INACTIVE(ptp, false); } return false; case TCSM_NOTIFIED_TCN: if(dry_run) /* state change */ return true; TCSM_to_NOTIFIED_TC(ptp); return false; case TCSM_DETECTED: /* return; */ case TCSM_NOTIFIED_TC: /* return; */ case TCSM_PROPAGATING: /* return; */ case TCSM_ACKNOWLEDGED: if(dry_run) /* state change */ return true; TCSM_to_ACTIVE(ptp); return false; case TCSM_ACTIVE: active_port = (roleRoot == ptp->role) || (roleDesignated == ptp->role) || (roleMaster == ptp->role); if(!active_port || prt->operEdge) { if(dry_run) /* state change */ return true; TCSM_to_LEARNING(ptp, false /* actual run */); return false; } if((0 == ptp->MSTID) && prt->rcvdTcn) { if(dry_run) /* state change */ return true; TCSM_to_NOTIFIED_TCN(ptp); return false; } if(ptp->rcvdTc) { if(dry_run) /* state change */ return true; TCSM_to_NOTIFIED_TC(ptp); return false; } if(ptp->tcProp/* && !prt->operEdge */) { if(dry_run) /* state change */ return true; TCSM_to_PROPAGATING(ptp); return false; } if((0 == ptp->MSTID) && prt->rcvdTcAck) { if(dry_run) /* state change */ return true; TCSM_to_ACKNOWLEDGED(ptp); return false; } return false; } return false; } /* Execute BEGIN state. We do not define BEGIN variable * but instead xxx_state_machines_begin execute begin state * abd do one step out of it */ static void tree_state_machines_begin(tree_t *tree) { bridge_t *br = tree->bridge; per_tree_port_t *ptp; if(!br->bridgeEnabled) return; /* 13.32 Port Information state machine */ FOREACH_PTP_IN_TREE(ptp, tree) { ptp->start_time = br->uptime; /* 12.8.2.2.3 b) */ PISM_begin(ptp); } /* 13.33 Port Role Selection state machine */ PRSSM_begin(tree); /* 13.34 Port Role Transitions state machine */ FOREACH_PTP_IN_TREE(ptp, tree) PRTSM_begin(ptp); /* 13.35 Port State Transition state machine */ FOREACH_PTP_IN_TREE(ptp, tree) PSTSM_begin(ptp); /* 13.36 Topology Change state machine */ FOREACH_PTP_IN_TREE(ptp, tree) TCSM_begin(ptp); br_state_machines_run(br); } static void prt_state_machines_begin(port_t *prt) { bridge_t *br = prt->bridge; tree_t *tree; per_tree_port_t *ptp; if(!br->bridgeEnabled) return; /* 13.28 Port Receive state machine */ PRSM_begin(prt); /* 13.29 Port Protocol Migration state machine */ PPMSM_begin(prt); /* 13.30 Bridge Detection state machine */ BDSM_begin(prt); /* 13.31 Port Transmit state machine */ PTSM_begin(prt); /* 13.32 Port Information state machine */ FOREACH_PTP_IN_PORT(ptp, prt) { ptp->start_time = br->uptime; /* 12.8.2.2.3 b) */ PISM_begin(ptp); } /* 13.33 Port Role Selection state machine */ FOREACH_TREE_IN_BRIDGE(tree, br) PRSSM_run(tree, false /* actual run */); /* 13.34 Port Role Transitions state machine */ FOREACH_PTP_IN_PORT(ptp, prt) PRTSM_begin(ptp); /* 13.35 Port State Transition state machine */ FOREACH_PTP_IN_PORT(ptp, prt) PSTSM_begin(ptp); /* 13.36 Topology Change state machine */ FOREACH_PTP_IN_PORT(ptp, prt) TCSM_begin(ptp); br_state_machines_run(br); } static void br_state_machines_begin(bridge_t *br) { port_t *prt; per_tree_port_t *ptp; tree_t *tree; if(!br->bridgeEnabled) return; /* 13.28 Port Receive state machine */ FOREACH_PORT_IN_BRIDGE(prt, br) PRSM_begin(prt); /* 13.29 Port Protocol Migration state machine */ FOREACH_PORT_IN_BRIDGE(prt, br) PPMSM_begin(prt); /* 13.30 Bridge Detection state machine */ FOREACH_PORT_IN_BRIDGE(prt, br) BDSM_begin(prt); /* 13.31 Port Transmit state machine */ FOREACH_PORT_IN_BRIDGE(prt, br) PTSM_begin(prt); /* 13.32 Port Information state machine */ FOREACH_PORT_IN_BRIDGE(prt, br) { FOREACH_PTP_IN_PORT(ptp, prt) { ptp->start_time = br->uptime; /* 12.8.2.2.3 b) */ PISM_begin(ptp); } } /* 13.33 Port Role Selection state machine */ FOREACH_TREE_IN_BRIDGE(tree, br) PRSSM_begin(tree); /* 13.34 Port Role Transitions state machine */ FOREACH_PORT_IN_BRIDGE(prt, br) { FOREACH_PTP_IN_PORT(ptp, prt) PRTSM_begin(ptp); } /* 13.35 Port State Transition state machine */ FOREACH_PORT_IN_BRIDGE(prt, br) { FOREACH_PTP_IN_PORT(ptp, prt) PSTSM_begin(ptp); } /* 13.36 Topology Change state machine */ FOREACH_PORT_IN_BRIDGE(prt, br) { FOREACH_PTP_IN_PORT(ptp, prt) TCSM_begin(ptp); } br_state_machines_run(br); } /* Run each state machine. * Return false iff all state machines in dry run indicate that * state will not be changed. Otherwise return true. */ static bool __br_state_machines_run(bridge_t *br, bool dry_run) { port_t *prt; per_tree_port_t *ptp; tree_t *tree; /* Check if bridge assurance timer expires */ FOREACH_PORT_IN_BRIDGE(prt, br) { if(prt->portEnabled && assurancePort(prt) && (0 == prt->brAssuRcvdInfoWhile) && !prt->BaInconsistent ) { if(dry_run) /* state change */ return true; prt->BaInconsistent = true; ERROR_PRTNAME(prt->bridge, prt, "Bridge assurance inconsistent"); } } /* 13.28 Port Receive state machine */ FOREACH_PORT_IN_BRIDGE(prt, br) { if(PRSM_run(prt, dry_run) && dry_run) return true; } /* 13.29 Port Protocol Migration state machine */ FOREACH_PORT_IN_BRIDGE(prt, br) { if(PPMSM_run(prt, dry_run) && dry_run) return true; } /* 13.30 Bridge Detection state machine */ FOREACH_PORT_IN_BRIDGE(prt, br) { if(BDSM_run(prt, dry_run) && dry_run) return true; } /* 13.31 Port Transmit state machine */ FOREACH_PORT_IN_BRIDGE(prt, br) { if(PTSM_run(prt, dry_run) && dry_run) return true; } /* 13.32 Port Information state machine */ FOREACH_PORT_IN_BRIDGE(prt, br) { FOREACH_PTP_IN_PORT(ptp, prt) { if(PISM_run(ptp, dry_run) && dry_run) return true; } } /* 13.33 Port Role Selection state machine */ FOREACH_TREE_IN_BRIDGE(tree, br) { if(PRSSM_run(tree, dry_run) && dry_run) return true; } /* 13.34 Port Role Transitions state machine */ FOREACH_PORT_IN_BRIDGE(prt, br) { FOREACH_PTP_IN_PORT(ptp, prt) { if(PRTSM_run(ptp, dry_run) && dry_run) return true; } } /* 13.35 Port State Transition state machine */ FOREACH_PORT_IN_BRIDGE(prt, br) { FOREACH_PTP_IN_PORT(ptp, prt) { if(PSTSM_run(ptp, dry_run) && dry_run) return true; } } /* 13.36 Topology Change state machine */ FOREACH_PORT_IN_BRIDGE(prt, br) { FOREACH_PTP_IN_PORT(ptp, prt) { if(TCSM_run(ptp, dry_run) && dry_run) return true; } } return false; } /* Run state machines until their state stabilizes. * Do not consume more than 1 second. */ static void br_state_machines_run(bridge_t *br) { struct timespec tv, tv_end; signed long delta; if(!br->bridgeEnabled) return; clock_gettime(CLOCK_MONOTONIC, &tv_end); ++(tv_end.tv_sec); do { if(!__br_state_machines_run(br, true /* dry run */)) return; __br_state_machines_run(br, false /* actual run */); /* Check for the timeout */ clock_gettime(CLOCK_MONOTONIC, &tv); if(0 < (delta = tv.tv_sec - tv_end.tv_sec)) return; if(0 == delta) { delta = tv.tv_nsec - tv_end.tv_nsec; if(0 < delta) return; } } while(true); } mstpd-mstpd-8995e5d/mstp.h000066400000000000000000000554561516242366700155330ustar00rootroot00000000000000/* * mstp.h State machines from IEEE 802.1Q-2005 * * This program is free software; you can redistribute it and/or * modify it under the terms of the 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: Vitalii Demianets */ #ifndef MSTP_H #define MSTP_H #include #include #include "bridge_ctl.h" #include "list.h" /* #define HMAC_MDS_TEST_FUNCTIONS */ /* Useful macro for counting number of elements in array */ #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) /* * assign() and cmp() macros that also do strict type-checking. See the * "unnecessary" pointer comparison. * NOTE: potential double-evaluation of the first argument in assign macro! * It is the price for type-safety ;) */ #define assign(x, y) ({ \ typeof(x) _assign1 = (x); \ typeof(y) _assign2 = (y); \ (void)(&_assign1 == &_assign2); \ (x) = _assign2; }) #define _ncmp(x, y) ({ \ typeof(x) _cmp1 = (x); \ typeof(y) _cmp2 = (y); \ (void)(&_cmp1 == &_cmp2); \ memcmp(&_cmp1, &_cmp2, sizeof(_cmp1)); }) #define cmp(x, _op, y) (_ncmp((x), (y)) _op 0) /* 13.7, Table 13-1 */ #define HMAC_KEY {0x13, 0xAC, 0x06, 0xA6, 0x2E, 0x47, 0xFD, 0x51, \ 0xF9, 0x5D, 0x2B, 0xA2, 0x43, 0xCD, 0x03, 0x46} extern void hmac_md5(const unsigned char * text, int text_len, const unsigned char * key, int key_len, void * digest); #ifdef HMAC_MDS_TEST_FUNCTIONS extern bool MD5TestSuite(void); #endif /* HMAC_MDS_TEST_FUNCTIONS */ #define MAX_PORT_NUMBER 4095 #define MAX_VID 4094 #define MAX_FID 4095 #define MAX_MSTID 4094 /* MAX_xxx_MSTIS: CIST not counted */ #define MAX_STANDARD_MSTIS 64 #define MAX_IMPLEMENTATION_MSTIS 63 /* 13.37.1 */ #define MAX_PATH_COST 200000000u typedef union { __u64 u; struct { __be16 priority; __u8 mac_address[ETH_ALEN]; } __attribute__((packed)) s; } bridge_identifier_t; typedef __be16 port_identifier_t; /* These macros work well for both PortID and BridgeID */ #define GET_PRIORITY_FROM_IDENTIFIER(id) (((__u8 *)(&(id)))[0] & 0xF0) #define SET_PRIORITY_IN_IDENTIFIER(pri, id) do{ \ __u8 *first_octet = (__u8 *)(&(id)); \ *first_octet &= 0x0F; \ *first_octet |= (pri) & 0xF0; \ }while(0) #define CONFIGURATION_NAME_LEN 32 #define CONFIGURATION_DIGEST_LEN 16 typedef union { __u8 a[1 + CONFIGURATION_NAME_LEN + 2 + CONFIGURATION_DIGEST_LEN]; struct { __u8 selector; /* always 0 */ __u8 configuration_name[CONFIGURATION_NAME_LEN]; __be16 revision_level; __u8 configuration_digest[CONFIGURATION_DIGEST_LEN]; } __attribute__((packed)) s; } __attribute__((packed)) mst_configuration_identifier_t; typedef struct { bridge_identifier_t RRootID; __be32 IntRootPathCost; bridge_identifier_t DesignatedBridgeID; port_identifier_t DesignatedPortID; /* not used for MSTIs, only for CIST */ bridge_identifier_t RootID; __be32 ExtRootPathCost; } port_priority_vector_t; typedef struct { __u8 remainingHops; /* not used for MSTIs, only for CIST */ __u8 Forward_Delay; __u8 Max_Age; __u8 Message_Age; __u8 Hello_Time; } times_t; typedef struct { /* see bpduFlagOffset_t enum for offsets of flag bits */ __u8 flags; bridge_identifier_t mstiRRootID; __be32 mstiIntRootPathCost; /* only bits 7..4, bits 3..0 are zero on Tx and ignored on Rx */ __u8 bridgeIdentifierPriority; /* only bits 7..4, bits 3..0 are zero on Tx and ignored on Rx */ __u8 portIdentifierPriority; __u8 remainingHops; } __attribute__((packed)) msti_configuration_message_t; typedef struct { /* always zero for the Spanning Tree BPDUs */ __be16 protocolIdentifier; /* protoSTP for the Config and TCN * protoRSTP for the RST * protoMSTP for the MST * (see protocol_version_t enum) */ __u8 protocolVersion; /* values are defined in bpduType_t enum */ __u8 bpduType; /* TCN BPDU ends here */ /* see bpduFlagOffset_t enum for offsets of flag bits */ __u8 flags; bridge_identifier_t cistRootID; __be32 cistExtRootPathCost; bridge_identifier_t cistRRootID; port_identifier_t cistPortID; __u8 MessageAge[2]; __u8 MaxAge[2]; __u8 HelloTime[2]; __u8 ForwardDelay[2]; /* Config BPDU ends here */ __u8 version1_len; /* always zero */ /* RST BPDU ends here */ __be16 version3_len; mst_configuration_identifier_t mstConfigurationIdentifier; __be32 cistIntRootPathCost; bridge_identifier_t cistBridgeID; __u8 cistRemainingHops; msti_configuration_message_t mstConfiguration[MAX_STANDARD_MSTIS]; } __attribute__((packed)) bpdu_t; #define TCN_BPDU_SIZE offsetof(bpdu_t, flags) #define CONFIG_BPDU_SIZE offsetof(bpdu_t, version1_len) #define RST_BPDU_SIZE offsetof(bpdu_t, version3_len) #define MST_BPDU_SIZE_WO_MSTI_MSGS offsetof(bpdu_t, mstConfiguration) #define MST_BPDU_VER3LEN_WO_MSTI_MSGS (MST_BPDU_SIZE_WO_MSTI_MSGS \ - offsetof(bpdu_t, mstConfigurationIdentifier)) typedef enum { OtherInfo, SuperiorDesignatedInfo, RepeatedDesignatedInfo, InferiorDesignatedInfo, InferiorRootAlternateInfo } port_info_t; typedef enum { ioDisabled, ioMine, ioAged, ioReceived } port_info_origin_t; typedef enum { roleDisabled, roleRoot, roleDesignated, roleAlternate, roleBackup, roleMaster } port_role_t; typedef enum { encodedRoleMaster = 0, encodedRoleAlternateBackup = 1, encodedRoleRoot = 2, encodedRoleDesignated = 3 } port_encoded_role_t; typedef enum { protoSTP = 0, protoRSTP = 2, protoMSTP = 3 } protocol_version_t; typedef enum { bpduTypeConfig = 0, bpduTypeRST = 2, bpduTypeTCN = 128 } bpduType_t; typedef enum { offsetTc = 0, offsetProposal = 1, offsetRole = 2, /* actually, role is coded in two-bit field */ offsetRole1 = 3, /* second bit of two-bit role field */ offsetLearnig = 4, offsetForwarding = 5, offsetAgreement = 6, offsetTcAck = 7 /* in MSTI Configuration Message flags bit7 is used for Master flag */ #define offsetMaster offsetTcAck } bpduFlagOffset_t; #define BPDU_FLAGS_ROLE_SET(role) (((role) & 3) << offsetRole) #define BPDU_FLAGS_ROLE_GET(flags) (((flags) >> offsetRole) & 3) typedef enum { p2pAuto, p2pForceTrue, p2pForceFalse } admin_p2p_t; /* 13.28 Port Receive state machine */ typedef enum { PRSM_DISCARD, PRSM_RECEIVE } PRSM_states_t; /* 13.29 Port Protocol Migration state machine */ typedef enum { PPMSM_CHECKING_RSTP, PPMSM_SELECTING_STP, PPMSM_SENSING } PPMSM_states_t; /* 13.30 Bridge Detection state machine */ typedef enum { BDSM_EDGE, BDSM_NOT_EDGE } BDSM_states_t; /* 13.31 Port Transmit state machine */ typedef enum { PTSM_TRANSMIT_INIT, PTSM_TRANSMIT_CONFIG, PTSM_TRANSMIT_TCN, PTSM_TRANSMIT_RSTP, PTSM_TRANSMIT_PERIODIC, PTSM_IDLE } PTSM_states_t; /* 13.32 Port Information state machine */ /* #define PISM_ENABLE_LOG */ typedef enum { PISM_DISABLED, PISM_AGED, PISM_UPDATE, PISM_SUPERIOR_DESIGNATED, PISM_REPEATED_DESIGNATED, PISM_INFERIOR_DESIGNATED, PISM_NOT_DESIGNATED, PISM_OTHER, PISM_CURRENT, PISM_RECEIVE } PISM_states_t; /* 13.33 Port Role Selection state machine */ typedef enum { PRSSM_INIT_TREE, PRSSM_ROLE_SELECTION } PRSSM_states_t; /* 13.34 Port Role Transitions state machine */ /* #define PRTSM_ENABLE_LOG */ typedef enum { /* Disabled Port role transitions */ PRTSM_INIT_PORT, PRTSM_DISABLE_PORT, PRTSM_DISABLED_PORT, /* MasterPort role transitions */ PRTSM_MASTER_PROPOSED, PRTSM_MASTER_AGREED, PRTSM_MASTER_SYNCED, PRTSM_MASTER_RETIRED, PRTSM_MASTER_FORWARD, PRTSM_MASTER_LEARN, PRTSM_MASTER_DISCARD, PRTSM_MASTER_PORT, /* RootPort role transitions */ PRTSM_ROOT_PROPOSED, PRTSM_ROOT_AGREED, PRTSM_ROOT_SYNCED, PRTSM_REROOT, PRTSM_ROOT_FORWARD, PRTSM_ROOT_LEARN, PRTSM_REROOTED, PRTSM_ROOT_PORT, /* DesignatedPort role transitions */ PRTSM_DESIGNATED_PROPOSE, PRTSM_DESIGNATED_AGREED, PRTSM_DESIGNATED_SYNCED, PRTSM_DESIGNATED_RETIRED, PRTSM_DESIGNATED_FORWARD, PRTSM_DESIGNATED_LEARN, PRTSM_DESIGNATED_DISCARD, PRTSM_DESIGNATED_PORT, /* AlternatePort and BackupPort role transitions */ PRTSM_BLOCK_PORT, PRTSM_BACKUP_PORT, PRTSM_ALTERNATE_PROPOSED, PRTSM_ALTERNATE_AGREED, PRTSM_ALTERNATE_PORT } PRTSM_states_t; /* 13.35 Port State Transition state machine */ typedef enum { PSTSM_DISCARDING, PSTSM_LEARNING, PSTSM_FORWARDING } PSTSM_states_t; /* 13.36 Topology Change state machine */ typedef enum { TCSM_INACTIVE, TCSM_LEARNING, TCSM_DETECTED, TCSM_NOTIFIED_TCN, TCSM_NOTIFIED_TC, TCSM_PROPAGATING, TCSM_ACKNOWLEDGED, TCSM_ACTIVE } TCSM_states_t; /* * Following standard-defined variables are not defined as variables. * Their functionality is implemented indirectly by other means: * - BEGIN, tick, ageingTime. */ typedef struct { struct list_head list; /* anchor in global list of bridges */ /* List of all ports */ struct list_head ports; /* List of all tree instances, first in list (trees.next) is CIST */ struct list_head trees; #define GET_CIST_TREE(br) list_entry((br)->trees.next, tree_t, bridge_list) bool bridgeEnabled; /* Per-bridge configuration parameters */ mst_configuration_identifier_t MstConfigId; /* 13.24.b */ protocol_version_t ForceProtocolVersion; /* 13.22.e */ __u8 MaxHops; /* 13.22.o */ __u8 Forward_Delay; /* 13.22.f */ __u8 Max_Age; /* 13.22.i */ /* The 802.1Q-2005 (13.22.j) says that this parameter is substituted by * the per-port Hello Time, but we still need it for compatibility * with old STP implementations. */ __u8 Hello_Time; unsigned int Transmit_Hold_Count; /* 13.22.g */ unsigned int Migrate_Time; /* 13.22.h */ unsigned int Ageing_Time; /* 8.8.3 */ __u16 vid2fid[MAX_VID + 1]; __be16 fid2mstid[MAX_FID + 1]; /* not in standard */ unsigned int uptime; sysdep_br_data_t sysdeps; } bridge_t; typedef struct { struct list_head bridge_list; /* anchor in bridge's list of trees */ bridge_t * bridge; __be16 MSTID; /* 0 == CIST */ /* List of the per-port data structures for this tree instance */ struct list_head ports; /* 13.23.(c,f,g) Per-bridge per-tree variables */ bridge_identifier_t BridgeIdentifier; port_identifier_t rootPortId; port_priority_vector_t rootPriority; /* 13.23.d This is totally calculated from BridgeIdentifier */ port_priority_vector_t BridgePriority; /* 13.23.e Some waste of space here, as MSTIs only use * remainingHops member of the struct times_t, * but saves extra checks and improves readability */ times_t BridgeTimes, rootTimes; /* 12.8.1.1.3.(b,c,d) */ unsigned int time_since_topology_change; unsigned int topology_change_count; bool topology_change; char topology_change_port[IFNAMSIZ]; char last_topology_change_port[IFNAMSIZ]; /* State machines */ PRSSM_states_t PRSSM_state; } tree_t; typedef struct { struct list_head br_list; /* anchor in bridge's list of ports */ bridge_t * bridge; __be16 port_number; /* List of all tree instances, first in list (trees.next) is CIST. * List is sorted by MSTID (by insertion procedure MSTP_IN_create_msti). */ struct list_head trees; #define GET_CIST_PTP_FROM_PORT(prt) \ list_entry((prt)->trees.next, per_tree_port_t, port_list) /* 13.21.(a,b,c) Per-port timers */ unsigned int mdelayWhile, helloWhen, edgeDelayWhile; /* 13.24.(b,c,e,f,g,j,k,l,m,n,o,p,q,r,aw) Per-port variables */ unsigned int txCount; bool operEdge, portEnabled, infoInternal, rcvdInternal; bool mcheck, rcvdBpdu, rcvdRSTP, rcvdSTP, rcvdTcAck, rcvdTcn, sendRSTP; bool tcAck, newInfo, newInfoMsti; /* 6.4.3 */ bool operPointToPointMAC; /* Per-port configuration parameters */ bool restrictedRole, restrictedTcn; /* 13.24.(h,i) */ __u32 ExternalPortPathCost; /* 13.22.p */ __u32 AdminExternalPortPathCost; /* 0 = calculate from speed */ admin_p2p_t AdminP2P; /* 6.4.3 */ bool AdminEdgePort; /* 13.22.k */ bool AutoEdge; /* 13.22.m */ bool BpduGuardPort; bool BpduGuardError; bool NetworkPort; bool BaInconsistent; bool dontTxmtBpdu; bool bpduFilterPort; unsigned int rapidAgeingWhile; unsigned int brAssuRcvdInfoWhile; /* State machines */ PRSM_states_t PRSM_state; PPMSM_states_t PPMSM_state; BDSM_states_t BDSM_state; PTSM_states_t PTSM_state; /* Copy of the received BPDU */ bpdu_t rcvdBpduData; int rcvdBpduNumOfMstis; bool deleted; sysdep_if_data_t sysdeps; unsigned int num_rx_bpdu_filtered; unsigned int num_rx_bpdu; unsigned int num_rx_tcn; unsigned int num_tx_bpdu; unsigned int num_tx_tcn; unsigned int num_trans_fwd; unsigned int num_trans_blk; } port_t; typedef struct { struct list_head port_list; /* anchor in port's list of trees */ struct list_head tree_list; /* anchor in tree's list of per-port data */ port_t *port; tree_t *tree; __be16 MSTID; /* 0 == CIST */ int state; /* BR_STATE_xxx */ /* 13.21.(d,e,f,g,h) Per-port per-tree timers */ unsigned int fdWhile, rrWhile, rbWhile, tcWhile, rcvdInfoWhile; /* 13.24.(s,t,u,v,w,x,y,z,aa,ab,ac,ad,ae,af,ag,ai,aj,ak,ap,as,at,au,av) * Per-port per-tree variables */ bool agree, agreed, disputed, forward, forwarding, learn, learning; port_info_t rcvdInfo; port_info_origin_t infoIs; bool proposed, proposing, rcvdMsg, rcvdTc, reRoot, reselect, selected; bool fdbFlush, tcProp, updtInfo, sync, synced; port_identifier_t portId; port_role_t role, selectedRole; /* 13.24.(al,an,aq) Some waste of space here, as MSTIs don't use * RootID and ExtRootPathCost members of the struct port_priority_vector_t, * but saves extra checks and improves readability */ port_priority_vector_t designatedPriority, msgPriority, portPriority; /* 13.24.(am,ao,ar) Some waste of space here, as MSTIs only use * remainingHops member of the struct times_t, * but saves extra checks and improves readability */ times_t designatedTimes, msgTimes, portTimes; /* 13.24.(ax,ay) Per-port per-MSTI variables, not applicable to CIST */ bool master, mastered; /* Per-port per-tree configuration parameters */ __u32 InternalPortPathCost; /* 13.22.q */ __u32 AdminInternalPortPathCost; /* 0 = calculate from speed */ /* not in standard, used for calculation of port uptime */ unsigned int start_time; /* State machines */ PISM_states_t PISM_state; PRTSM_states_t PRTSM_state; PSTSM_states_t PSTSM_state; TCSM_states_t TCSM_state; /* Auxiliary flag, helps preventing infinite recursion */ bool calledFromFlushRoutine; /* Pointer to the corresponding MSTI Configuration Message * in the port->rcvdBpduData */ msti_configuration_message_t *rcvdMstiConfig; } per_tree_port_t; /* External events (inputs) */ bool MSTP_IN_bridge_create(bridge_t *br, __u8 *macaddr); bool MSTP_IN_port_create_and_add_tail(port_t *prt, __u16 portno); void MSTP_IN_delete_port(port_t *prt); void MSTP_IN_delete_bridge(bridge_t *br); void MSTP_IN_set_bridge_address(bridge_t *br, __u8 *macaddr); void MSTP_IN_set_bridge_enable(bridge_t *br, bool up); void MSTP_IN_set_port_enable(port_t *prt, bool up, int speed, int duplex); void MSTP_IN_one_second(bridge_t *br); void MSTP_IN_all_fids_flushed(per_tree_port_t *ptp); void MSTP_IN_rx_bpdu(port_t *prt, bpdu_t *bpdu, int size); bool MSTP_IN_set_vid2fid(bridge_t *br, __u16 vid, __u16 fid); bool MSTP_IN_set_all_vids2fids(bridge_t *br, __u16 *vids2fids); bool MSTP_IN_set_fid2mstid(bridge_t *br, __u16 fid, __u16 mstid); bool MSTP_IN_set_all_fids2mstids(bridge_t *br, __u16 *fids2mstids); bool MSTP_IN_get_mstilist(bridge_t *br, int *num_mstis, __u16 *mstids); bool MSTP_IN_create_msti(bridge_t *br, __u16 mstid); bool MSTP_IN_delete_msti(bridge_t *br, __u16 mstid); void MSTP_IN_set_mst_config_id(bridge_t *br, __u16 revision, __u8 *name); /* External actions (outputs) */ void MSTP_OUT_set_state(per_tree_port_t *ptp, int new_state); void MSTP_OUT_flush_all_fids(per_tree_port_t *ptp); void MSTP_OUT_set_ageing_time(port_t *prt, unsigned int ageingTime); void MSTP_OUT_tx_bpdu(port_t *prt, bpdu_t *bpdu, int size); void MSTP_OUT_shutdown_port(port_t *prt); /* Structures for communicating with user */ /* 12.8.1.1 Read CIST Bridge Protocol Parameters */ typedef struct { bridge_identifier_t bridge_id; unsigned int time_since_topology_change; unsigned int topology_change_count; bool topology_change; char topology_change_port[IFNAMSIZ]; char last_topology_change_port[IFNAMSIZ]; bridge_identifier_t designated_root; unsigned int root_path_cost; port_identifier_t root_port_id; __u8 root_max_age; __u8 root_forward_delay; __u8 bridge_max_age; __u8 bridge_forward_delay; unsigned int tx_hold_count; protocol_version_t protocol_version; bridge_identifier_t regional_root; unsigned int internal_path_cost; bool enabled; /* not in standard */ unsigned int Ageing_Time; __u8 max_hops; __u8 bridge_hello_time; } CIST_BridgeStatus; void MSTP_IN_get_cist_bridge_status(bridge_t *br, CIST_BridgeStatus *status); /* 12.8.1.2 Read MSTI Bridge Protocol Parameters */ typedef struct { bridge_identifier_t bridge_id; unsigned int time_since_topology_change; unsigned int topology_change_count; bool topology_change; char topology_change_port[IFNAMSIZ]; char last_topology_change_port[IFNAMSIZ]; bridge_identifier_t regional_root; unsigned int internal_path_cost; port_identifier_t root_port_id; } MSTI_BridgeStatus; void MSTP_IN_get_msti_bridge_status(tree_t *tree, MSTI_BridgeStatus *status); /* 12.8.1.3 Set CIST Bridge Protocol Parameters */ typedef struct { __u8 bridge_max_age; bool set_bridge_max_age; __u8 bridge_forward_delay; bool set_bridge_forward_delay; /* Superseded by MSTP_IN_set_msti_bridge_config for the CIST. * __u8 bridge_priority; * bool set_bridge_priority; */ protocol_version_t protocol_version; bool set_protocol_version; unsigned int tx_hold_count; bool set_tx_hold_count; __u8 max_hops; bool set_max_hops; __u8 bridge_hello_time; bool set_bridge_hello_time; unsigned int bridge_ageing_time; bool set_bridge_ageing_time; } CIST_BridgeConfig; int MSTP_IN_set_cist_bridge_config(bridge_t *br, CIST_BridgeConfig *cfg); /* 12.8.1.4 Set MSTI Bridge Protocol Parameters */ /* No need in special structure for single parameter Bridge Priority */ int MSTP_IN_set_msti_bridge_config(tree_t *tree, __u8 bridge_priority); /* 12.8.2.1 Read CIST Port Parameters */ typedef struct { unsigned int uptime; int state; /* BR_STATE_xxx */ port_identifier_t port_id; __u32 admin_external_port_path_cost; /* not in standard. 0 = auto */ __u32 external_port_path_cost; bridge_identifier_t designated_root; /* from portPriority */ __u32 designated_external_cost; /* from portPriority */ bridge_identifier_t designated_bridge; /* from portPriority */ port_identifier_t designated_port; /* from portPriority */ bool tc_ack; /* tcAck */ __u8 port_hello_time; /* from portTimes */ bool admin_edge_port; bool auto_edge_port; /* not in standard */ bool oper_edge_port; /* 802.1Q-2005 wants here MAC_Enabled & MAC_Operational. We don't know * neither of these. Return portEnabled and feel happy. */ bool enabled; admin_p2p_t admin_p2p; bool oper_p2p; bool restricted_role; bool restricted_tcn; port_role_t role; bool disputed; bridge_identifier_t designated_regional_root; /* from portPriority */ __u32 designated_internal_cost; /* from portPriority */ __u32 admin_internal_port_path_cost; /* not in standard. 0 = auto */ __u32 internal_port_path_cost; /* not in standard */ bool bpdu_guard_port; bool bpdu_guard_error; bool bpdu_filter_port; bool network_port; bool ba_inconsistent; unsigned int num_rx_bpdu_filtered; unsigned int num_rx_bpdu; unsigned int num_rx_tcn; unsigned int num_tx_bpdu; unsigned int num_tx_tcn; unsigned int num_trans_fwd; unsigned int num_trans_blk; bool rcvdBpdu; bool rcvdRSTP; bool rcvdSTP; bool rcvdTcAck; bool rcvdTcn; bool sendRSTP; } CIST_PortStatus; void MSTP_IN_get_cist_port_status(port_t *prt, CIST_PortStatus *status); /* 12.8.2.2 Read MSTI Port Parameters */ typedef struct { unsigned int uptime; int state; /* BR_STATE_xxx */ port_identifier_t port_id; __u32 admin_internal_port_path_cost; /* not in standard. 0 = auto */ __u32 internal_port_path_cost; bridge_identifier_t designated_regional_root; /* from portPriority */ __u32 designated_internal_cost; /* from portPriority */ bridge_identifier_t designated_bridge; /* from portPriority */ port_identifier_t designated_port; /* from portPriority */ port_role_t role; bool disputed; } MSTI_PortStatus; void MSTP_IN_get_msti_port_status(per_tree_port_t *ptp, MSTI_PortStatus *status); /* 12.8.2.3 Set CIST port parameters */ typedef struct { __u32 admin_external_port_path_cost; /* not in standard. 0 = auto */ bool set_admin_external_port_path_cost; /* Superseded by MSTP_IN_set_msti_port_config for the CIST. * __u32 admin_internal_port_path_cost; * bool set_admin_internal_port_path_cost; * * __u8 port_priority; * bool set_port_priority; */ bool admin_edge_port; bool set_admin_edge_port; bool auto_edge_port; /* not in standard */ bool set_auto_edge_port; admin_p2p_t admin_p2p; bool set_admin_p2p; bool restricted_role; bool set_restricted_role; bool restricted_tcn; bool set_restricted_tcn; bool bpdu_guard_port; bool set_bpdu_guard_port; bool network_port; bool set_network_port; bool dont_txmt; bool set_dont_txmt; bool bpdu_filter_port; bool set_bpdu_filter_port; } CIST_PortConfig; int MSTP_IN_set_cist_port_config(port_t *prt, CIST_PortConfig *cfg); /* 12.8.2.4 Set MSTI port parameters */ typedef struct { __u32 admin_internal_port_path_cost; /* 0 = auto */ bool set_admin_internal_port_path_cost; __u8 port_priority; bool set_port_priority; } MSTI_PortConfig; int MSTP_IN_set_msti_port_config(per_tree_port_t *ptp, MSTI_PortConfig *cfg); /* 12.8.2.5 Force BPDU Migration Check */ int MSTP_IN_port_mcheck(port_t *prt); #endif /* MSTP_H */ mstpd-mstpd-8995e5d/mstpd.spec000066400000000000000000000114221516242366700163630ustar00rootroot00000000000000Name: mstpd Summary: STP/RSTP/PVST+/MSTP Spanning Tree Protocol Daemon URL: https://github.com/mstpd/mstpd Version: 0.2.0 Release: 1%{?dist} License: GPLv2+ Group: System Environment/Daemons Source0: https://github.com/mstpd/mstpd/archive/%{version}/%{name}-%{version}.tar.gz %{?systemd_ordering} BuildRequires: gcc, make, systemd, pkgconfig, autoconf, automake Requires: bridge-utils Requires: iproute Requires: python3 %description This package provides a user-space daemon which replaces the STP handling that is built into the Linux kernel Ethernet bridge and adds support for RSTP and PVST+. This daemon also supports participating in MSTP. However, due to the way the Linux kernel implements its FIBs, it is not currently possible to map MSTP topologies onto Linux bridges. Therefore, mstpd will not actually block ports on Linux bridges when MSTP is used. %prep %setup -q %build ./autogen.sh %define _exec_prefix %{nil} %define _libexecdir /lib %configure --with-systemdunitdir=%{_unitdir} %make_build %install %make_install mkdir -p $RPM_BUILD_ROOT%{_prefix}/lib/NetworkManager/dispatcher.d install -m 755 -p utils/nm-dispatcher \ $RPM_BUILD_ROOT%{_prefix}/lib/NetworkManager/dispatcher.d/45-mstpd sed -i -e 's|/etc/network/interfaces|%{_sysconfdir}/sysconfig/network-scripts/bridge-stp|g' $RPM_BUILD_ROOT%{_libexecdir}/mstpctl-utils/ifquery sed -i -e 's|/etc/network/interfaces|%{_sysconfdir}/sysconfig/network-scripts/bridge-stp|g' $RPM_BUILD_ROOT%{_libexecdir}/mstpctl-utils/mstp_config_bridge mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/network-scripts cat <<'END' > $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/network-scripts/bridge-stp # To automatically configure mstpd at boot in RHEL/Fedora: # * If you are bridging VLANs / trunk ports, see # /usr/share/doc/mstpd/README.VLANs # * Create a standard /etc/sysconfig/network-scripts/ifcfg-... file for the # bridge. For example: # DEVICE=... # ONBOOT=yes # NM_CONTROLLED=no # BOOTPROTO=static # NOZEROCONF=yes # IPADDR=... # NETMASK=... # GATEWAY=... # IPV6INIT=yes # IPV6_AUTOCONF=no # IPV6ADDR=... # IPV6_DEFAULTGW=... # * Note that TYPE=Bridge and the additional ifcfg-ethX files for each interface # attached to the bridge (as described at the following link) are unnecessary # when using mstpd. If VLANs are not created on top of the bridge, these may # be present if desired, but they will simply cause the ifup-eth script to do # unnecessary work. If VLANs are created on top of the bridge, TYPE=Bridge # must not be used, as it will cause the VLANs to be brought up before the # bridge, which will fail. # https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Deployment_Guide/s2-networkscripts-interfaces_network-bridge.html # * Add the following line (uncommented) at the end of the bridge's ifcfg file: # [ "$0" = '/etc/sysconfig/network-scripts/ifup-eth' ] && /lib/mstpctl-utils/mstp_config_bridge "$DEVICE" # * Replace "br0" in the "iface" line below with the bridge's DEVICE name, and # adjust the mstpctl_* configuration options as needed. # See `man mstpctl-utils-interfaces` for documentation on the options. # To configure multiple bridges, add an additional "iface" line and associated # configuration options for each additional bridge. iface br0 mstpctl_ports eth0 eth1 mstpctl_stp on mstpctl_forcevers rstp mstpctl_maxwait auto mstpctl_hello 2 mstpctl_maxage 20 mstpctl_fdelay 15 mstpctl_txholdcount 6 mstpctl_maxhops 20 mstpctl_treeprio 8 #mstpctl_treeportprio eth2=8 #mstpctl_portpathcost eth1=0 eth2=0 #mstpctl_portadminedge eth1=no eth2=no #mstpctl_portautoedge eth1=yes eth2=yes #mstpctl_portp2p eth1=no eth2=no #mstpctl_portrestrrole eth1=no eth2=no #mstpctl_bpduguard eth1=no eth2=no #mstpctl_portrestrtcn eth1=no eth2=no #mstpctl_portnetwork eth1=no END %post %systemd_post %{name}.service %preun %systemd_preun %{name}.service %postun %systemd_postun_with_restart %{name}.service %files %defattr(-,root,root,-) %{_sbindir}/mstpd %{_sbindir}/mstpctl %{_sbindir}/bridge-stp %{_sbindir}/mstp_restart %config(noreplace) %{_sysconfdir}/bridge-stp.conf %config(noreplace) %{_sysconfdir}/sysconfig/network-scripts/bridge-stp %{_sysconfdir}/bash_completion.d/mstpctl %{_unitdir}/mstpd.service %{_prefix}/lib/NetworkManager/dispatcher.d/45-mstpd %{_libexecdir}/mstpctl-utils/mstpctl_restart_config %{_libexecdir}/mstpctl-utils/mstp_config_bridge %{_libexecdir}/mstpctl-utils/ifquery %{_libexecdir}/mstpctl-utils/ifupdown.sh %{_libexecdir}/mstpctl-utils/mstpctl-utils-functions.sh %doc %{_mandir}/man8/mstpctl.8.gz %doc %{_mandir}/man5/mstpctl-utils-interfaces.5.gz %doc %{_docdir}/mstpd/README.VLANs %doc %{_docdir}/mstpd/README %license %{_docdir}/mstpd/LICENSE mstpd-mstpd-8995e5d/netif_utils.c000066400000000000000000000111131516242366700170460ustar00rootroot00000000000000/***************************************************************************** Copyright (c) 2006 EMC Corporation. Copyright (c) 2011 Factor-SPE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. Authors: Srinivas Aji Authors: Vitalii Demianets ******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #ifndef SYSFS_CLASS_NET #define SYSFS_CLASS_NET "/sys/class/net" #endif static int netsock = -1; int netsock_init(void) { netsock = socket(AF_INET, SOCK_DGRAM, 0); if(0 > netsock) { ERROR("Couldn't open inet socket for ioctls: %m\n"); return -1; } return 0; } int get_hwaddr(char *ifname, __u8 *hwaddr) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); if(0 > ioctl(netsock, SIOCGIFHWADDR, &ifr)) { ERROR("%s: get hw address failed: %m", ifname); return -1; } memcpy(hwaddr, ifr.ifr_hwaddr.sa_data, ETH_ALEN); return 0; } int get_flags(char *ifname) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); if(0 > ioctl(netsock, SIOCGIFFLAGS, &ifr)) { ERROR("%s: get interface flags failed: %m", ifname); return -1; } return ifr.ifr_flags; } int if_shutdown(char *ifname) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); /* TODO: Let's hope -1 is not a valid flag combination */ if(-1 == (ifr.ifr_flags = get_flags(ifname))) { return -1; } ifr.ifr_flags &= ~IFF_UP; strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); if(0 > ioctl(netsock, SIOCSIFFLAGS, &ifr)) { ERROR("%s: set if_down flag failed: %m", ifname); return -1; } return 0; } int ethtool_get_speed_duplex(char *ifname, int *speed, int *duplex) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); struct ethtool_cmd ecmd; ecmd.cmd = ETHTOOL_GSET; ifr.ifr_data = (caddr_t)&ecmd; if(0 > ioctl(netsock, SIOCETHTOOL, &ifr)) { ERROR("Cannot get speed/duplex for %s: %m\n", ifname); return -1; } *speed = ethtool_cmd_speed(&ecmd); /* Ethtool speed is in Mbps */ *duplex = ecmd.duplex; /* We have same convention as ethtool. 0 = half, 1 = full */ return 0; } char *index_to_name(int index, char *name) { return if_indextoname(index, name); } char *index_to_port_name(int index, char *name) { return if_indextoname(index, name); } /********* Sysfs based utility functions *************/ /* This sysfs stuff might break with interface renames */ bool is_bridge(char *if_name) { char path[32 + IFNAMSIZ]; sprintf(path, SYSFS_CLASS_NET "/%s/bridge", if_name); return (0 == access(path, R_OK)); } int get_bridge_portno(char *if_name) { char path[32 + IFNAMSIZ]; sprintf(path, SYSFS_CLASS_NET "/%s/brport/port_no", if_name); char buf[128]; int fd; long res = -1; TSTM((fd = open(path, O_RDONLY)) >= 0, -1, "%m"); int l; TSTM((l = read(fd, buf, sizeof(buf) - 1)) >= 0, -1, "%m"); if(0 == l) { ERROR("Empty port index file"); goto out; } else if((sizeof(buf) - 1) == l) { ERROR("port_index file too long"); goto out; } buf[l] = 0; if('\n' == buf[l - 1]) buf[l - 1] = 0; char *end; res = strtoul(buf, &end, 0); if(0 != *end || INT_MAX < res) { ERROR("Invalid port index %s", buf); res = -1; } out: close(fd); return res; } mstpd-mstpd-8995e5d/netif_utils.h000066400000000000000000000031371516242366700170620ustar00rootroot00000000000000/***************************************************************************** Copyright (c) 2006 EMC Corporation. Copyright (c) 2011 Factor-SPE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. Authors: Srinivas Aji Authors: Vitalii Demianets ******************************************************************************/ #ifndef NETIF_UTILS_H #define NETIF_UTILS_H /* An inet socket for everyone to use for ifreqs. */ int netsock_init(void); int get_hwaddr(char *ifname, unsigned char *hwaddr); int get_flags(char *ifname); int if_shutdown(char *ifname); int ethtool_get_speed_duplex(char *ifname, int *speed, int *duplex); bool is_bridge(char *if_name); int get_bridge_portno(char *if_name); char *index_to_name(int index, char *name); char *index_to_port_name(int index, char *name); #endif /* NETIF_UTILS_H */ mstpd-mstpd-8995e5d/packet.c000066400000000000000000000123331516242366700157750ustar00rootroot00000000000000/***************************************************************************** Copyright (c) 2006 EMC Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. Authors: Srinivas Aji Authors: Stephen Hemminger ******************************************************************************/ /* #define PACKET_DEBUG */ #include #include #include #include #include #include #include #include #include #include "epoll_loop.h" #include "netif_utils.h" #include "bridge_ctl.h" #include "packet.h" #include "log.h" static struct epoll_event_handler packet_event; #ifdef PACKET_DEBUG static void dump_packet(const unsigned char *buf, int cc) { int i, j; for(i = 0; i < cc; i += 16) { for(j = 0; j < 16 && i + j < cc; ++j) printf(" %02x", buf[i + j]); printf("\n"); } printf("\n"); fflush(stdout); } #endif /* * To send/receive Spanning Tree packets we use PF_PACKET because * it allows the filtering we want but gives raw data */ void packet_send(int ifindex, const struct iovec *iov, int iov_count, int len) { int l; struct sockaddr_ll sl = { .sll_family = AF_PACKET, .sll_protocol = __constant_cpu_to_be16(ETH_P_802_2), .sll_ifindex = ifindex, .sll_halen = ETH_ALEN, }; if(iov_count > 0 && iov[0].iov_len > ETH_ALEN) memcpy(&sl.sll_addr, iov[0].iov_base, ETH_ALEN); struct msghdr msg = { .msg_name = &sl, .msg_namelen = sizeof(sl), .msg_iov = (struct iovec *)iov, .msg_iovlen = iov_count, .msg_control = NULL, .msg_controllen = 0, .msg_flags = 0, }; #ifdef PACKET_DEBUG printf("Transmit Dst index %d %02x:%02x:%02x:%02x:%02x:%02x\n", sl.sll_ifindex, sl.sll_addr[0], sl.sll_addr[1], sl.sll_addr[2], sl.sll_addr[3], sl.sll_addr[4], sl.sll_addr[5]); { int i; for(i = 0; i < iov_count; ++i) dump_packet(iov[i].iov_base, iov[i].iov_len); } #endif l = sendmsg(packet_event.fd, &msg, 0); if(l < 0) { if(errno != EWOULDBLOCK) ERROR("send failed: %m"); } else if(l != len) ERROR("short write in sendto: %d instead of %d", l, len); } static void packet_rcv(uint32_t events, struct epoll_event_handler *h) { int cc; unsigned char buf[2048]; struct sockaddr_ll sl; socklen_t salen = sizeof sl; cc = recvfrom(h->fd, &buf, sizeof(buf), 0, (struct sockaddr *) &sl, &salen); if(cc <= 0) { ERROR("recvfrom failed: %m"); return; } #ifdef PACKET_DEBUG printf("Receive Src ifindex %d %02x:%02x:%02x:%02x:%02x:%02x\n", sl.sll_ifindex, sl.sll_addr[0], sl.sll_addr[1], sl.sll_addr[2], sl.sll_addr[3], sl.sll_addr[4], sl.sll_addr[5]); dump_packet(buf, cc); #endif bridge_bpdu_rcv(sl.sll_ifindex, buf, cc); } /* Berkeley Packet filter code to filter out spanning tree packets. from tcpdump -s 1152 -dd stp */ static struct sock_filter stp_filter[] = { { 0x28, 0, 0, 0x0000000c }, { 0x25, 3, 0, 0x000005dc }, { 0x30, 0, 0, 0x0000000e }, { 0x15, 0, 1, 0x00000042 }, { 0x6, 0, 0, 0x00000480 }, { 0x6, 0, 0, 0x00000000 }, }; /* * Open up a raw packet socket to catch all 802.2 packets. * and install a packet filter to only see STP (SAP 42) * * Since any bridged devices are already in promiscious mode * no need to add multicast address. */ int packet_sock_init(void) { int s; struct sock_fprog prog = { .len = sizeof(stp_filter) / sizeof(stp_filter[0]), .filter = stp_filter, }; s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_802_2)); if(s < 0) { ERROR("socket failed: %m"); return -1; } if(setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)) < 0) ERROR("setsockopt packet filter failed: %m"); else if(fcntl(s, F_SETFL, O_NONBLOCK) < 0) ERROR("fcntl set nonblock failed: %m"); else { int prio = TC_PRIO_CONTROL; if(setsockopt(s, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio)) < 0) ERROR("setsockopt priority failed, BPDU delivery may be unreliable: %m"); packet_event.fd = s; packet_event.handler = packet_rcv; if(0 == add_epoll(&packet_event)) return 0; } close(s); return -1; } mstpd-mstpd-8995e5d/packet.h000066400000000000000000000023451516242366700160040ustar00rootroot00000000000000/***************************************************************************** Copyright (c) 2006 EMC Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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. The full GNU General Public License is included in this distribution in the file called LICENSE. Authors: Srinivas Aji ******************************************************************************/ #ifndef PACKET_SOCK_H #define PACKET_SOCK_H #include void packet_send(int ifindex, const struct iovec *iov, int iov_count, int len); int packet_sock_init(void); #endif /* PACKET_SOCK_H */ mstpd-mstpd-8995e5d/utils/000077500000000000000000000000001516242366700155205ustar00rootroot00000000000000mstpd-mstpd-8995e5d/utils/bash_completion000066400000000000000000000045551516242366700206220ustar00rootroot00000000000000# bash completion for mstpctl -*- shell-script -*- _mstpctl() { local cur words cword _init_completion || return local command=${words[1]} case $cword in 1) COMPREPLY=( $( compgen -W " addbridge createtree deletetree \ delbridge debuglevel portmcheck setmstconfid setvid2fid \ setfid2mstid setmaxage setfdelay setmaxhops setforcevers \ settxholdcount settreeprio setportpathcost setportadminedge \ setportautoedge setportp2p setportrestrrole setportrestrtcn \ setbpduguard settreeportprio settreeportcost showbridge \ showmstilist showmstconfid showvid2fid showfid2mstid showport \ showportdetail showtree showtreeport sethello \ setageing setportnetwork setportbpdufilter" -- "$cur" ) ) ;; 2) case $command in debuglevel|showall) ;; *) COMPREPLY=( $( compgen -W "$( brctl show | \ grep 'yes\|no' | awk '{print $1}')" -- "$cur" ) ) esac ;; 3) case $command in showport|showportdetail|showtreeport|showportpathcode|\ setportadminedge|setportautoedge|setportp2p|\ setportrestrrole|setportrestrtcn|portmcheck|\ settreeportprio|settreeportcost|setportnetwork|\ setportbpdufilter) COMPREPLY=( $( compgen -W "$(for x in \ `ls /sys/class/net/${words[2]}/brif/`; do echo $x; \ done)" -- "$cur" ) ) ;; setforcevers) COMPREPLY=( $( compgen -W 'mstp rstp stp' -- "$cur" ) ) ;; esac ;; 4) case $command in setportadminedge|setportautoedge|setportrestrrole|\ setportrestrtcn|setbpduguard|setportnetwork|setportbpdufilter) COMPREPLY=( $( compgen -W 'yes no' -- "$cur" ) ) ;; setportp2p) COMPREPLY=( $(compgen -W 'yes no auto' -- "$cur" ) ) ;; esac ;; esac } && complete -F _mstpctl -o default mstpctl # ex: ts=4 sw=4 et filetype=sh mstpd-mstpd-8995e5d/utils/ifquery000077500000000000000000000020431516242366700171310ustar00rootroot00000000000000#!/usr/bin/python3 # Crudely emulate ifquery on systems that don't have it. import sys import glob def parse_config(interfaces_file, interface): f = open(interfaces_file) intf = '' found = False for line in f: str = line.strip().split(' ') len_str = len(str) if 2 > len_str: continue if str[0].startswith('#'): continue # Check if the source keyword is present and extract the file if str[0] == 'source': for sfile in glob.glob(str[1]): parse_config(sfile, interface) if str[0] == 'iface': intf = str[1] continue # Check if this is the relevant interface. if intf != interface: continue found = True # Print configuration. print((str[0]+": "+" ".join(str[1:]))) f.close() if not found: print("%s: unknown interface %s" % (sys.argv[0], interface)) sys.exit(1) return parse_config('/etc/network/interfaces', sys.argv[1]) mstpd-mstpd-8995e5d/utils/ifupdown.sh.in000077500000000000000000000303251516242366700203220ustar00rootroot00000000000000#!/bin/sh # This script should be symlinked into /etc/network/ip-pre-up.d/ and # /etc/network/ip-post-down.d/ on Debian/Ubuntu systems so that it will be run # by ifupdown when configuring network interfaces. # # This script is also used by `mstp_config_bridge`. # `mstp_config_bridge` is used by `/sbin/bridge-stp` when # `mstpctl_restart_config` or `mstp_restart` or # `/sbin/bridge-stp restart_config` or `/sbin/bridge-stp restart` are called to # automatically reconfigure bridges. # `mstp_config_bridge` may also be used and to configure bridges on systems # without ifupdown (on systems that are not based on Debian or Ubuntu). # # Have a look at /usr/share/doc/bridge-utils/README.Debian if you want # more info about the way in which bridges are set up on Debian. # # Author: Satish Ashok, if [ "$IF_MSTPCTL_STP" != 'on' ]; then exit 0 fi mstpctl='@mstpctlfile@' if [ ! -x "$mstpctl" ]; then exit 0 fi brcmd="$(command -v bridge)" if [ -z "$brcmd" ] || [ ! -x "$brcmd" ]; then echo "'bridge' binary does not exist or is not executable" >&2 exit 2 fi ip="$(command -v ip)" if [ -z "$ip" ] || [ ! -x "$ip" ]; then echo "'ip' binary does not exist or is not executable" >&2 exit 2 fi # shellcheck disable=SC1091 . '@utilsfuncfile@' || exit 2 case "$IF_MSTPCTL_PORTS" in '') exit 0 ;; none) INTERFACES='' ;; *) INTERFACES="$IF_MSTPCTL_PORTS" ;; esac # Previous work (create the interface) if [ "$MODE" = 'start' ] && [ ! -d "/sys/class/net/$IFACE" ]; then "$ip" link add "$IFACE" type bridge || exit 2 # Previous work (stop the interface) elif [ "$MODE" = 'stop' ]; then "$ip" link set dev "$IFACE" down || exit 2 fi # shellcheck disable=SC2086 unset all_interfaces && # $INTERFACES should be word split into multiple arguments to # mstpctl_parse_ports. Therefore it should not be quoted here. mstpctl_parse_ports $INTERFACES | while read -r i; do for port in $i; do # We attach and configure each port of the bridge if [ "$MODE" = 'start' ] && [ ! -d "/sys/class/net/$IFACE/brif/$port" ]; then if [ -x /etc/network/if-pre-up.d/vlan ]; then IFACE="$port" /etc/network/if-pre-up.d/vlan fi if [ "$IF_BRIDGE_HW" ]; then "$ip" link set dev "$port" down; "$ip" link set dev "$port" address "$IF_BRIDGE_HW" fi if [ -f "/proc/sys/net/ipv6/conf/$port/disable_ipv6" ]; then echo 1 > "/proc/sys/net/ipv6/conf/$port/disable_ipv6" fi "$ip" link set dev "$port" master "$IFACE" && "$ip" link set dev "$port" up # We detach each port of the bridge elif [ "$MODE" = 'stop' ] && [ -d "/sys/class/net/$IFACE/brif/$port" ]; then "$ip" link set dev "$port" down && "$ip" link set "$port" nomaster && \ if [ -x /etc/network/if-post-down.d/vlan ]; then IFACE="$port" /etc/network/if-post-down.d/vlan fi if [ -f "/proc/sys/net/ipv6/conf/$port/disable_ipv6" ]; then echo 0 > "/proc/sys/net/ipv6/conf/$port/disable_ipv6" fi fi done done # We finish setting up the bridge if [ "$MODE" = 'start' ]; then # This triggers the kernel to run `/sbin/bridge-stp start $IFACE` "$ip" link set "$IFACE" type bridge stp_state 1 # `mstpctl addbridge $IFACE` must be called before this script continues. # If mstpd is already running then /sbin/bridge-stp will call # `mstpctl addbridge $IFACE` before # `ip link set type bridge stp_state 1` returns. # If mstpd is not already running then /sbin/bridge-stp will start it and call # `mstpctl addbridge $IFACE` as a delayed background process, in which case it # may not run before `ip link set type bridge stp_state 1` returns. # It should not hurt to call `mstpctl addbridge $IFACE` twice, so call it # again to ensure that it has been called before continuing. # See the code and comments in /sbin/bridge-stp for more details. "$mstpctl" addbridge "$IFACE" # If MAXAGE should be increased, then FDELAY must be increased before it. # On the other hand, to reduce FDELAY, we must first reduce MAXAGE. # To accommodate both orderings, we check the current value of FDELAY # and act so that curfddly >= age is maintained. age=0 if [ "$IF_MSTPCTL_MAXAGE" ]; then age="$IF_MSTPCTL_MAXAGE" fi curfwddly="$( "$mstpctl" showbridge "$IFACE" bridge-forward-delay )" curfwddly=$((curfwddly+curfwddly)) if [ "$curfwddly" -gt "$age" ]; then [ "$age" -gt 0 ] && "$mstpctl" setmaxage "$IFACE" "$age" if [ "$IF_MSTPCTL_FDELAY" ]; then "$mstpctl" setfdelay "$IFACE" "$IF_MSTPCTL_FDELAY" fi else if [ "$IF_MSTPCTL_FDELAY" ]; then "$mstpctl" setfdelay "$IFACE" "$IF_MSTPCTL_FDELAY" fi [ "$age" -gt 0 ] && "$mstpctl" setmaxage "$IFACE" "$age" fi if [ "$IF_MSTPCTL_MAXHOPS" ]; then "$mstpctl" setmaxhops "$IFACE" "$IF_MSTPCTL_MAXHOPS" fi if [ "$IF_MSTPCTL_TXHOLDCOUNT" ]; then "$mstpctl" settxholdcount "$IFACE" "$IF_MSTPCTL_TXHOLDCOUNT" fi if [ "$IF_MSTPCTL_FORCEVERS" ]; then "$mstpctl" setforcevers "$IFACE" "$IF_MSTPCTL_FORCEVERS" fi if [ "$IF_MSTPCTL_TREEPRIO" ]; then "$mstpctl" settreeprio "$IFACE" 0 "$IF_MSTPCTL_TREEPRIO" fi if [ "$IF_MSTPCTL_PORTPATHCOST" ]; then portpathcosts="$(echo "$IF_MSTPCTL_PORTPATHCOST" | tr '\n' ' ' | tr -s ' ')" for portpathcost in $portpathcosts; do port="$(echo "$portpathcost" | cut -d '=' -f1)" pathcost="$(echo "$portpathcost" | cut -d '=' -f2)" if [ -n "$port" ] && [ -n "$pathcost" ]; then "$mstpctl" setportpathcost "$IFACE" "$port" "$pathcost" fi done fi if [ "$IF_MSTPCTL_PORTADMINEDGE" ]; then portadminedges="$(echo "$IF_MSTPCTL_PORTADMINEDGE" | tr '\n' ' ' | tr -s ' ')" for portadminedge in $portadminedges; do port="$(echo "$portadminedge" | cut -d '=' -f1)" adminedge="$(echo "$portadminedge" | cut -d '=' -f2)" if [ -n "$port" ] && [ -n "$adminedge" ]; then "$mstpctl" setportadminedge "$IFACE" "$port" "$adminedge" fi done fi if [ "$IF_MSTPCTL_PORTAUTOEDGE" ]; then portautoedges="$(echo "$IF_MSTPCTL_PORTAUTOEDGE" | tr '\n' ' ' | tr -s ' ')" for portautoedge in $portautoedges; do port="$(echo "$portautoedge" | cut -d '=' -f1)" autoedge="$(echo "$portautoedge" | cut -d '=' -f2)" if [ -n "$port" ] && [ -n "$autoedge" ]; then "$mstpctl" setportautoedge "$IFACE" "$port" "$autoedge" fi done fi if [ "$IF_MSTPCTL_PORTP2P" ]; then portp2ps="$(echo "$IF_MSTPCTL_PORTP2P" | tr '\n' ' ' | tr -s ' ')" for portp2p in $portp2ps; do port="$(echo "$portp2p" | cut -d '=' -f1)" p2p="$(echo "$portp2p" | cut -d '=' -f2)" if [ -n "$port" ] && [ -n "$p2p" ]; then "$mstpctl" setportp2p "$IFACE" "$port" "$p2p" fi done fi if [ "$IF_MSTPCTL_PORTRESTRROLE" ]; then portrestrroles="$(echo "$IF_MSTPCTL_PORTRESTRROLE" | tr '\n' ' ' | tr -s ' ')" for portrestrrole in $portrestrroles; do port="$(echo "$portrestrrole" | cut -d '=' -f1)" restrrole="$(echo "$portrestrrole" | cut -d '=' -f2)" if [ -n "$port" ] && [ -n "$restrrole" ]; then "$mstpctl" setportrestrrole "$IFACE" "$port" "$restrrole" fi done fi if [ "$IF_MSTPCTL_PORTRESTRTCN" ]; then portrestrtcns="$(echo "$IF_MSTPCTL_PORTRESTRTCN" | tr '\n' ' ' | tr -s ' ')" for portrestrtcn in $portrestrtcns; do port="$(echo "$portrestrtcn" | cut -d '=' -f1)" restrtcn="$(echo "$portrestrtcn" | cut -d '=' -f2)" if [ -n "$port" ] && [ -n "$restrtcn" ]; then "$mstpctl" setportrestrtcn "$IFACE" "$port" "$restrtcn" fi done fi if [ "$IF_MSTPCTL_BPDUGUARD" ]; then portbpduguards="$(echo "$IF_MSTPCTL_BPDUGUARD" | tr '\n' ' ' | tr -s ' ')" for portbpduguard in $portbpduguards; do port="$(echo "$portbpduguard" | cut -d '=' -f1)" bpduguard="$(echo "$portbpduguard" | cut -d '=' -f2)" if [ -n "$port" ] && [ -n "$bpduguard" ]; then "$mstpctl" setbpduguard "$IFACE" "$port" "$bpduguard" fi done fi if [ "$IF_MSTPCTL_PORTBPDUFILTER" ]; then portbpdufilters="$(echo "$IF_MSTPCTL_PORTBPDUFILTER" | tr '\n' ' ' | tr -s ' ')" for portbpdufilter in $portbpdufilters; do port="$(echo "$portbpdufilter" | cut -d '=' -f1)" bpdufilter="$(echo "$portbpdufilter" | cut -d '=' -f2)" if [ -n "$port" ] && [ -n "$bpdufilter" ]; then "$mstpctl" setportbpdufilter "$IFACE" "$port" "$bpdufilter" fi done fi if [ "$IF_MSTPCTL_TREEPORTPRIO" ]; then treeportprios="$(echo "$IF_MSTPCTL_TREEPORTPRIO" | tr '\n' ' ' | tr -s ' ')" for treeportprio in $treeportprios; do treeport="$(echo "$treeportprio" | cut -d '=' -f1)" prio="$(echo "$treeportprio" | cut -d '=' -f2)" if [ -n "$treeport" ] && [ -n "$prio" ]; then "$mstpctl" settreeportprio "$IFACE" "$treeport" 0 "$prio" fi done fi if [ "$IF_MSTPCTL_TREEPORTCOST" ]; then treeportcosts="$(echo "$IF_MSTPCTL_TREEPORTCOST" | tr '\n' ' ' | tr -s ' ')" for treeportcost in $treeportcosts; do treeport="$(echo "$treeportcost" | cut -d '=' -f1)" cost="$(echo "$treeportcost" | cut -d '=' -f2)" if [ -n "$treeport" ] && [ -n "$cost" ]; then "$mstpctl" settreeportcost "$IFACE" "$treeport" 0 "$cost" fi done fi if [ "$IF_MSTPCTL_HELLO" ]; then "$mstpctl" sethello "$IFACE" "$IF_MSTPCTL_HELLO" fi if [ "$IF_MSTPCTL_AGEING" ]; then "$mstpctl" setageing "$IFACE" "$IF_MSTPCTL_AGEING" fi if [ "$IF_MSTPCTL_PORTNETWORK" ]; then portnetworks="$(echo "$IF_MSTPCTL_PORTNETWORK" | tr '\n' ' ' | tr -s ' ')" for portnetwork in $portnetworks; do port="$(echo "$portnetwork" | cut -d '=' -f1)" network="$(echo "$portnetwork" | cut -d '=' -f2)" if [ -n "$port" ] && [ -n "$network" ]; then "$mstpctl" setportnetwork "$IFACE" "$port" "$network" fi done fi # We activate the bridge "$ip" link set dev "$IFACE" up # Calculate the maximum time to wait for STP to converge maxwait='' if [ -n "$IF_MSTPCTL_MAXWAIT" ] && [ "$IF_MSTPCTL_MAXWAIT" != 'auto' ]; then # if [ "$IF_MSTPCTL_MAXWAIT" is a number ]; then if [ "$IF_MSTPCTL_MAXWAIT" = "${IF_MSTPCTL_MAXWAIT%[!0-9]*}" ]; then maxwait="$IF_MSTPCTL_MAXWAIT" else echo "Ignoring invalid mstpctl_maxwait value '$maxwait'" >&2 fi fi if [ -z "$maxwait" ]; then root_forward_delay="$("$mstpctl" showbridge "$IFACE" forward-delay)" if [ -z "$root_forward_delay" ] || [ "$root_forward_delay" != "${root_forward_delay%[!0-9]*}" ]; then root_forward_delay=0 fi bridge_forward_delay="$("$mstpctl" showbridge "$IFACE" bridge-forward-delay)" if [ -z "$bridge_forward_delay" ] || [ "$root_forward_delay" != "${bridge_forward_delay%[!0-9]*}" ]; then bridge_forward_delay=0 fi if [ "$root_forward_delay" -gt "$bridge_forward_delay" ]; then maxwait="$root_forward_delay" else maxwait="$bridge_forward_delay" fi maxwait=$((maxwait*2+1)) fi # Wait for STP to converge if [ "$maxwait" -ne 0 ]; then echo echo "Waiting for STP on bridge '$IFACE' to converge (mstpctl_maxwait is $maxwait seconds)." # Use 0.1 delay if available sleep 0.1 2>/dev/null && maxwait=$((maxwait*10)) count=0 ; transitioned='' ; converged='' while [ -z "$converged" ] && [ "$count" -lt "$maxwait" ]; do sleep 0.1 2>/dev/null || sleep 1 count=$((count+1)) # Converged if all ports are either 'forwarding', 'blocking', or # 'disabled', except if all ports are 'disabled' and we have yet to see # any port transition to any other valid state. converged=true for i in $("$brcmd" link show | grep "master $IFACE" | sed -n 's/^.*state[ ]*\([^ ]*\).*$/\1/p'); do if [ "$i" = 'listening' ] || [ "$i" = 'learning' ]; then transitioned=true converged='' break elif [ "$i" = 'forwarding' ] || [ "$i" = 'blocking' ]; then transitioned=true elif [ "$i" != 'disabled' ] || [ -z "$transitioned" ]; then converged='' fi done done if [ -z "$converged" ]; then echo "Timed out waiting for STP on bridge '$IFACE' to converge" >&2 fi fi # Finally we destroy the interface elif [ "$MODE" = 'stop' ]; then brstp='@bridgestpfile@' if [ -x "$brstp" ]; then "$brstp" "$IFACE" stop fi "$ip" link del "$IFACE" fi mstpd-mstpd-8995e5d/utils/mstp_config_bridge.in000077500000000000000000000050461516242366700217040ustar00rootroot00000000000000#!/bin/sh # # This script uses `ifquery` to read the config in # /etc/network/interfaces and then runs the mstpctl-utils # `ifupdown.sh` script to configure the specified bridge in mstpd. # # This is used by the `/sbin/bridge-stp` script when called as # `mstpctl_restart_config` or `mstp_restart` or # `/sbin/bridge-stp restart_config` or `/sbin/bridge-stp restart` to # automatically reconfigure bridges. # # This may also be used to configure mstpd on systems without ifupdown (on # systems that are not based on Debian or Ubuntu). # Parse arguments. if [ $# -ne 1 ]; then echo "Usage: $0 " >&2 exit 1 fi Bridge="$1" # Make sure this script is being run as root. if [ "$(id -u)" != '0' ]; then echo 'This script must be run as root' >&2 exit 1 fi # Ensure that we have a sane umask. umask 022 # Ensure that we have a sane PATH. PATH='/sbin:/usr/sbin:/bin:/usr/bin' export PATH # The mstp ifupdown.sh script will not work properly unless mstpctl, bridge, # and ip exist and are executable. mstpctl='@mstpctlfile@' if [ ! -x "$mstpctl" ]; then echo "'mstpctl' binary does not exist or is not executable" >&2 exit 2 fi brcmd="$(command -v bridge)" if [ -z "$brcmd" ] || [ ! -x "$brcmd" ]; then echo "'bridge' binary does not exist or is not executable" >&2 exit 2 fi ip="$(command -v ip)" if [ -z "$ip" ] || [ ! -x "$ip" ]; then echo "'ip' binary does not exist or is not executable" >&2 exit 2 fi # Find ifquery. ifquery="$(command -v ifquery 2>/dev/null)" if [ -z "$ifquery" ]; then # If the real ifquery is not installed, use our emulator. ifquery='@ifqueryfile@' fi if [ ! -x "$ifquery" ]; then echo "'ifquery' binary does not exist or is not executable" >&2 exit 2 fi # Run ifquery. Out="$("$ifquery" "$Bridge" 2>/dev/null)" ; Err=$? if [ $Err -ne 0 ]; then echo "Skipping configuration of bridge '$Bridge' that is not configured in" echo '/etc/network/interfaces' exit 0 fi # Read the interface config and generate the environment variables that are # typically generated by ifupdown. export IFACE="$Bridge" export LOGICAL="$Bridge" export ADDRFAM='inet' export METHOD='manual' export MODE='start' export PHASE='post-up' export VERBOSITY=0 eval "$( IFS= ; echo "$Out" | \ while read -r Line; do OptName="${Line%%: *}" # Strip everything after the first ': ' OptName="IF_$(echo "$OptName" | tr '[:lower:]-' '[:upper:]_')" # Uppercase OptVal="${Line#*: }" # Strip everything before the first ': ' echo "export $OptName=\"$OptVal\"" done )" # Call the mstpctl-utils ifupdown.sh script. '@ifupdownfile@' mstpd-mstpd-8995e5d/utils/mstpctl-utils-functions.sh000066400000000000000000000017071516242366700227130ustar00rootroot00000000000000#!/bin/sh mstpctl_parse_ports() { while [ "${1+set}" = set ] do # For compatibility: the 'all' option. case $1 in all) shift && set -- regex eth.\* em.\* 'p[0-9].*' noregex "$@" ;; esac # Primitive state machine... case $1-$(uname -s) in regex-Linux) all_interfaces=$(sed -n 's%^[\ ]*\([^:]*\):.*$%\1%p' < /proc/net/dev) shift ;; regex-*) echo "$0 needs to be ported for your $(uname -s) system. " \ "Trying to continue nevertheless." >&2 shift ;; noregex-*) unset all_interfaces shift ;; esac case ${all_interfaces+regex}-${1+set} in regex-set) # The following interface specification are to be parsed as regular # expressions against all interfaces the system provides. i=$(grep -E "^$1$" << EOAI $all_interfaces EOAI ) shift ;; *-set) # Literal interfaces. i=$1 shift ;; *) # No interface specification is following. i= ;; esac echo "$i" done } mstpd-mstpd-8995e5d/utils/mstpctl-utils-interfaces.5000066400000000000000000000136031516242366700225560ustar00rootroot00000000000000.\" -*- nroff -*- .\" macros .de EX \" Begin Example . IP . ft CW . nf . ne \\$1 .. .de EE \" End Example . ft P . fi . PP .. .TH MSTPCTL-UTILS-INTERFACES 5 "April 29 2013" "mstpctl-utils" "File formats" .SH NAME mstpctl-utils-interfaces \- mstpctl-utils extensions for the .BR interfaces (5) file format .SH DESCRIPTION /etc/network/interfaces contains network interface configuration information for the .BR ifup (8) and .BR ifdown (8) commands. This manpage describes the mstpctl extensions to the standard .BR interfaces (5) file format. .P The main extension is the mstpctl_ports option, with it you describe that the interface is a bridge and what ports does it have. Spanning Tree protocol parameters can also be specified. Currently multiple spanning trees are not supported by this extension (although mstpd/mstpctl supports MSTP without restrictions). This means that all settings here are applied to the CIST tree. .P We'll see this with an example: .EX auto br0 iface br0 inet static address 12.0.0.3 netmask 255.255.255.0 mstpctl_ports swp1 swp2 swp3 swp4 mstpctl_stp on mstpctl_maxwait auto mstpctl_maxage 20 mstpctl_fdelay 15 mstpctl_maxhops 20 mstpctl_txholdcount 6 mstpctl_forcevers rstp mstpctl_portpathcost swp1=0 swp2=0 mstpctl_portadminedge swp1=no swp2=no swp4=yes mstpctl_portautoedge swp1=yes swp2=yes mstpctl_portp2p swp1=no swp2=no mstpctl_portrestrrole swp1=no swp2=no mstpctl_bpduguard swp1=no swp2=no swp4=yes mstpctl_portrestrtcn swp1=no swp2=no mstpctl_treeprio 8 mstpctl_treeportprio swp3=8 mstpctl_hello 2 mstpctl_portnetwork swp1=no mstpctl_portbpdufilter swp4=yes .EE .SH IFACE OPTIONS The below interface options for configuring STP parameters on an STP enabled bridge can be specified in /etc/network/interfaces. .TP .BI mstpctl_ports " interface specification" this option must exist to specify the bridge and the ports which need to be added to the bridge. .RS .EX mstpctl_ports swp1 swp4 .EE .P Regular expressions can be used to select a number of interfaces .EX mstpctl_ports regex (eth|vif).* .EE This means to evaluate (as in .BR egrep (1)) the expressions that follow after "regex" until either the end or a "noregex" statement is reached. .SS .TP .BI mstpctl_stp " yes|no" turn spanning tree protocol on/off, default is no. .TP .BI mstpctl_maxwait " time|auto" set max time in seconds to wait for initial STP convergence before allowing ifup to exit, default is auto (=\fIfdelay\fP*2+1). .TP .BI mstpctl_maxage " time" set max message age to \fItime\fP seconds, default is 20. .TP .BI mstpctl_fdelay " time" set bridge forward delay to \fItime\fP seconds, default is 15. .TP .BI mstpctl_maxhops " max_hops" set bridge maximum hops to \fImax_hops\fP, default is 20. .TP .BI mstpctl_forcevers " mstp|rstp|stp" forces the bridge to use specific STP protocol version. mstp is the default. .TP .BI mstpctl_txholdcount " tx_hold_count" set bridge transmit hold count to \fItx_hold_count\fP, default is 6. .TP .BI mstpctl_treeprio " priority" set bridge tree priority, \fIpriority\fP is between 0 and 15. Bridge priority affects bridge id, lowest priority bridge will be the root. Note that traditionally bridge priority is a multiple of 4096 and ranges from 0 to 15*4096 = 61440. mstpd is different - it throws out that useless "multiple of 4096" rule and expresses bridge priority in natural units (0-15). Default is 8. .TP .BI mstpctl_portpathcost " port cost" set path cost for a port, default is 0 (= auto), \fIport\fP is the name of the interface to which this setting applies. .TP .BI mstpctl_portadminedge " port yes|no" Enables/disables the admin edge state of the \fIport\fP, default is no. .TP .BI mstpctl_portautoedge " port yes|no" Enables/disables the auto transition to/from edge state of the \fIport\fP, default is yes. .TP .BI mstpctl_portp2p " port yes|no|auto" Enables/disables the point2point detection mode of the \fIport\fP, default is auto. .TP .BI mstpctl_portrestrrole " port yes|no" Enables/disables the restrictions on the \fIport\fP's ability to take root role, default is no (i.e. no restrictions on the port's role). .TP .BI mstpctl_portrestrtcn " port yes|no" Enables/disables the restrictions on the \fIport\fP's ability to propagate received topology change notification, default is no (i.e. no restrictions on the TCN propagation). .TP .BI mstpctl_bpduguard " port yes|no" Enables/disables the bpduguard configuration of the \fIport\fP, default is no. .TP .BI mstpctl_treeportprio " port priority" sets the \fIport\fP's priority to \fIpriority\fP. The priority value is a number between 0 and 15. Note that traditionally port priority is a multiple of 16 and ranges from 0 to 15*16 = 240. mstpd is different - it throws out that useless "multiple of 16" rule and expresses port priority in natural units (0-15). Default is 8. .TP .BI mstpctl_hello " time" set hello time to \fItime\fP seconds, default is 2. .TP .BI mstpctl_ageing " time" sets the ethernet (MAC) address ageing \fItime\fP, in seconds. Used only when protocol version is forced to STP, default is 300s. Note that this parameter differs from the other ones: it is only informational parameter. By setting it in the mstpd one do not change the real bridge's Ageing Time; it is supposed to be set as information to the mstpd that real Ageing Time in the real bridge was changed. .TP .BI mstpctl_portnetwork " port yes|no" Enables/disables the bridge assurance capability for a \fIport\fP, default is no. .TP .BI mstpctl_portbpdufilter " port yes|no" Enables/disables the bridge BPDU filter capability for a \fIport\fP, default is no. .RE .SH FILES .TP .I /etc/network/interfaces definitions of network interfaces See .BR interfaces (5) for more information. .RE .SH AUTHOR This manpage was written by Satish Ashok based on \fBbrctl-utils-interfaces\fP(5). .SH "SEE ALSO" .BR brctl (8), .BR interfaces (5), .BR ifup (8), .BR iwconfig (8), .BR run\-parts (8). mstpd-mstpd-8995e5d/utils/mstpctl.8000066400000000000000000000137101516242366700173010ustar00rootroot00000000000000.\" .\" This program is free software; you can redistribute it and/or modify .\" it under the terms of the GNU General Public License as published by .\" the Free Software Foundation; either version 2 of the License, or .\" (at your option) any later version. .\" .\" This program is distributed in the hope that it will be useful, .\" but WITHOUT ANY WARRANTY; without even the implied warranty of .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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., 675 Mass Ave, Cambridge, MA 02139, USA. .\" .\" .TH MSTPCTL 8 "April 29, 2013" "" "" .SH NAME mstpctl \- mstpd configuration .SH SYNOPSIS .BR "mstpctl [command]" .SH DESCRIPTION .B mstpctl is used to configure Multiple Spanning Tree daemon (mstpd). MSTPCTL is used for configuring STP parameters on bridges which have user-space STP enabled. Currently, STP is disabled by default on the bridge. To enable user-space STP, configure "brctl stp on" or "ip link set type bridge stp_state 1" while ensuring that /sbin/bridge-stp kernel helper script will return success (0) for this operation. .SH SPANNING TREE PROTOCOL CONFIGURATION .IR mstpctl(8) can be used for configuring certain spanning tree protocol parameters. For an explanation of these parameters, see the IEEE 802.1D-2004 and 802.1Q-2005(-2011) standards. .B mstpctl setmaxage sets the 's 'maximum age' to seconds, default is 20. .B mstpctl setfdelay