From: Vitalii Demianets Date: Thu, 22 Sep 2011 13:33:02 +0000 (+0000) Subject: Initial import X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1e6d2d09674c5759bedf65007e324f1eaf5cb2d2;p=people%2Fms%2Fmstpd.git Initial import git-svn-id: http://svn.code.sf.net/p/mstpd/code/trunk@1 fbe50366-0c72-4402-a84b-5d246361dba7 --- 1e6d2d09674c5759bedf65007e324f1eaf5cb2d2 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b7b5f53 --- /dev/null +++ b/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d7bc4ec --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +version := 0.01 + +DSOURCES = main.c epoll_loop.c brmon.c bridge_track.c libnetlink.c mstp.c \ + packet.c netif_utils.c ctl_socket_server.c hmac_md5.c + +DOBJECTS = $(DSOURCES:.c=.o) + +CTLSOURCES = ctl_main.c ctl_socket_client.c + +CTLOBJECTS = $(CTLSOURCES:.c=.o) + +CFLAGS += -Werror -O2 -D_REENTRANT -D__LINUX__ -DVERSION=$(version) -I. + +all: mstpd mstpctl + +mstpd: $(DOBJECTS) + $(CC) -o $@ $(DOBJECTS) + +mstpctl: $(CTLOBJECTS) + $(CC) -o $@ $(CTLOBJECTS) + +-include .depend + +clean: + rm -f *.o *~ .depend.bak mstpd mstpctl + +romfs: all + $(ROMFSINST) /sbin/mstpd + $(ROMFSINST) /sbin/mstpctl + $(ROMFSINST) /sbin/bridge-stp + +#depend: +# makedepend -I. -Y *.c -f .depend diff --git a/bridge-stp b/bridge-stp new file mode 100755 index 0000000..6bed90d --- /dev/null +++ b/bridge-stp @@ -0,0 +1,57 @@ +#!/bin/bash +# +# Script to start/stop spanning tree called from kernel +# Make sure umask is sane +umask 022 + +# Set up a default search path. +PATH="/sbin:/usr/sbin:/bin:/usr/bin" +export PATH + +if [ $# -ne 2 ]; then + echo "Usage: bridge-stp {start|stop}" + exit 1 +fi +bridge=$1 +service=mstpd +pid_file=/var/run/${service}.pid + +# Set this to the list of bridges for which MSTP should be used +MSTP_BRIDGES="br0" + +# Set $pid to pids from /var/run* for {program}. $pid should be declared +# local in the caller. +# Returns LSB exit code for the 'status' action. +checkpid() +{ + pid= + if [ -f "$1" ] ; then + local line p + read line < "$pid_file" + for p in $line ; do + [ -z "${p//[0-9]/}" -a -d "/proc/$p" ] && pid="$pid $p" + done + if [ -n "$pid" ]; then + return 0 + fi + return 1 # "Program is dead and /var/run pid file exists" + fi + return 3 # "Program is not running" +} + +case $2 in + start) + checkpid $pid_file || exit 1 + for b in $MSTP_BRIDGES; do + if [ "$bridge" == "$b" ]; then + exit 0; + fi + done + exit 1 ;; + stop) + exit 0 ;; + *) + echo "Unknown action:" $2 + echo "Usage: bridge-stp {start|stop}" + exit 1 +esac diff --git a/bridge_ctl.h b/bridge_ctl.h new file mode 100644 index 0000000..1d241ae --- /dev/null +++ b/bridge_ctl.h @@ -0,0 +1,92 @@ +/***************************************************************************** + 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, stp_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_set_state(int ifindex, int state); + +int bridge_notify(int br_index, int if_index, bool newlink, bool up); + +void bridge_bpdu_rcv(int ifindex, const unsigned char *data, int len); + +void bridge_one_second(void); + +#endif /* BRIDGE_CTL_H */ diff --git a/bridge_track.c b/bridge_track.c new file mode 100644 index 0000000..ba261ca --- /dev/null +++ b/bridge_track.c @@ -0,0 +1,760 @@ +/***************************************************************************** + 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 "bridge_ctl.h" +#include "netif_utils.h" +#include "packet.h" +#include "log.h" +#include "mstp.h" + +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_indextoname(if_index, br->sysdeps.name); + get_hwaddr(br->sysdeps.name, br->sysdeps.macaddr); + + INFO("Add bridge %s", br->sysdeps.name); + if(!MSTP_IN_bridge_create(br, br->sysdeps.macaddr)) + { + free(br); + return NULL; + } + + list_add_tail(&br->list, &bridges); + return br; +} + +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 *ifc; + TST((ifc = calloc(1, sizeof(*ifc))) != NULL, NULL); + + /* Init system dependent info */ + ifc->sysdeps.if_index = if_index; + if_indextoname(if_index, ifc->sysdeps.name); + get_hwaddr(ifc->sysdeps.name, ifc->sysdeps.macaddr); + + int portno; + if(0 > (portno = get_bridge_portno(ifc->sysdeps.name))) + { + ERROR("Couldn't get port number for %s", ifc->sysdeps.name); + free(ifc); + return NULL; + } + if((0 == portno) || (portno > MAX_PORT_NUMBER)) + { + ERROR("Port number for %s is invalid (%d)", ifc->sysdeps.name, portno); + free(ifc); + return NULL; + } + + INFO("Add iface %s as port#%d to bridge %s", ifc->sysdeps.name, + portno, br->sysdeps.name); + ifc->bridge = br; + if(!MSTP_IN_port_create_and_add_tail(ifc, portno)) + { + free(ifc); + return NULL; + } + + return ifc; +} + +static port_t * find_if(bridge_t * br, int if_index) +{ + port_t *ifc; + list_for_each_entry(ifc, &br->ports, br_list) + { + if(ifc->sysdeps.if_index == if_index) + return ifc; + } + return NULL; +} + +static inline void delete_if(port_t * ifc) +{ + list_del(&ifc->br_list); + MSTP_IN_delete_port(ifc); + free(ifc); +} + +static inline bool delete_if_byindex(bridge_t * br, int if_index) +{ + port_t *ifc; + if(!(ifc = find_if(br, if_index))) + return false; + delete_if(ifc); + return true; +} + +static bool delete_br_byindex(int if_index) +{ + bridge_t *br; + if(!(br = find_br(if_index))) + return false; + 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 int stp_enabled(bridge_t * br) +{ + char path[40 + IFNAMSIZ]; + sprintf(path, "/sys/class/net/%s/bridge/stp_state", br->sysdeps.name); + FILE *f = fopen(path, "r"); + int enabled = 0; + if(!f || (1 != fscanf(f, "%d", &enabled))) + ERROR("Can't read from %s", path); + fclose(f); + INFO("STP on %s state %d", br->sysdeps.name, enabled); + + return enabled == 2; /* ie user mode STP */ +} + +static void set_br_up(bridge_t * br, bool up) +{ + int stp_up = stp_enabled(br); + INFO("%s was %s stp was %s", br->sysdeps.name, + br->sysdeps.up ? "up" : "down", br->sysdeps.stp_up ? "up" : "down"); + INFO("Set bridge %s %s stp %s" , br->sysdeps.name, + up ? "up" : "down", stp_up ? "up" : "down"); + + bool changed = false; + + if(up != br->sysdeps.up) + { + br->sysdeps.up = up; + changed = true; + } + + if(br->sysdeps.stp_up != stp_up) + { + br->sysdeps.stp_up = stp_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 && br->sysdeps.stp_up); +} + +static void set_if_up(port_t * ifc, bool up) +{ + INFO("Port %s : %s", ifc->sysdeps.name, (up ? "up" : "down")); + int speed = -1; + int duplex = -1; + bool changed = false; + + if(check_mac_address(ifc->sysdeps.name, ifc->sysdeps.macaddr)) + { + /* MAC address changed */ + if(check_mac_address(ifc->bridge->sysdeps.name, + ifc->bridge->sysdeps.macaddr)) + { + /* Notify bridge address change */ + MSTP_IN_set_bridge_address(ifc->bridge, + ifc->bridge->sysdeps.macaddr); + } + } + + if(!up) + { /* Down */ + if(ifc->sysdeps.up) + { + ifc->sysdeps.up = false; + changed = true; + } + } + else + { /* Up */ + int r = ethtool_get_speed_duplex(ifc->sysdeps.name, &speed, &duplex); + if((r < 0) || (speed < 0)) + speed = 10; + if((r < 0) || (duplex < 0)) + duplex = 0; /* Assume half duplex */ + + if(speed != ifc->sysdeps.speed) + { + ifc->sysdeps.speed = speed; + changed = true; + } + if(duplex != ifc->sysdeps.duplex) + { + ifc->sysdeps.duplex = duplex; + changed = true; + } + if(!ifc->sysdeps.up) + { + ifc->sysdeps.up = true; + changed = true; + } + } + if(changed) + MSTP_IN_set_port_enable(ifc, ifc->sysdeps.up, ifc->sysdeps.speed, + ifc->sysdeps.duplex); +} + +/* br_index == if_index means: interface is bridge master */ +int bridge_notify(int br_index, int if_index, bool newlink, bool up) +{ + port_t *ifc; + bridge_t *br = NULL, *other_br; + + LOG("br_index %d, if_index %d, newlink %d, up %d", + br_index, if_index, newlink, up); + + if((br_index >= 0) && (br_index != if_index)) + { + if(!(br = find_br(br_index))) + br = create_br(br_index); + if(!br) + { + ERROR("Couldn't create data for bridge interface %d", br_index); + return -1; + } + int br_up = ethtool_get_link(br->sysdeps.name); + if(br_up >= 0) + set_br_up(br, !!br_up); + } + + if(br) + { + if(!(ifc = 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; + } + } + ifc = create_if(br, if_index); + } + if(!ifc) + { + ERROR("Couldn't create data for interface %d (master %d)", + if_index, br_index); + return -1; + } + if(!newlink) + { + delete_if(ifc); + return 0; + } + set_if_up(ifc, up); /* 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))) + { + if(!(br = create_br(br_index))) + { + ERROR("Couldn't create data for bridge interface %d", + br_index); + return -1; + } + } + 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 *ifc = NULL; + bridge_t *br; + + LOG("ifindex %d, len %d", if_index, len); + + list_for_each_entry(br, &bridges, list) + { + if((ifc = find_if(br, if_index))) + break; + } + if(!ifc) + return; + + /* sanity checks */ + TST(br == ifc->bridge,); + TST(ifc->sysdeps.up,); + if(!br->sysdeps.stp_up) + return; + + /* 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(ifc, + /* Don't include LLC header */ + (bpdu_t *)(data + sizeof(*h)), l - LLC_PDU_LEN_U); +} + +/* External actions for MSTP protocol */ + +void MSTP_OUT_set_state(per_tree_port_t *ptp, int new_state) +{ + char * state_name; + port_t *ifc = ptp->port; + bridge_t *br = ifc->bridge; + + switch(new_state) + { + case BR_STATE_LISTENING: + state_name = "listening"; + break; + case BR_STATE_LEARNING: + state_name = "learning"; + break; + case BR_STATE_FORWARDING: + state_name = "forwarding"; + break; + case BR_STATE_BLOCKING: + state_name = "blocking"; + break; + default: + ERROR_MSTINAME(br, ifc, ptp, "attempt to set invalid state %d", + new_state); + new_state = BR_STATE_DISABLED; + case BR_STATE_DISABLED: + state_name = "disabled"; + break; + } + + if(ptp->state == new_state) + return; + + /* TODO: command driver to put the br:port:tree into new state */ + + ptp->state = new_state; + INFO_MSTINAME(br, ifc, ptp, "entering %s state", 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) +{ + /* TODO: do real flushing. + * Make it asynchronous, with completion function calling + * MSTP_IN_all_fids_flushed(ptp) + */ + MSTP_IN_all_fids_flushed(ptp); +} + +/* ageingTime < 0 => command driver to use its internal setting */ +void MSTP_OUT_set_ageing_time(bridge_t * br, int ageingTime) +{ + /* TODO: do set new ageing time */ +} + +void MSTP_OUT_tx_bpdu(port_t * ifc, bpdu_t * bpdu, int size) +{ + char *bpdu_type; + bridge_t *br = ifc->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"; + } + + LOG_PRTNAME(br, ifc, "sending %s BPDU", bpdu_type); + + struct llc_header h; + memcpy(h.dest_addr, bridge_group_address, ETH_ALEN); + memcpy(h.src_addr, ifc->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(ifc->sysdeps.if_index, iov, 2, sizeof(h) + size); +} + +/* 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; + return MSTP_IN_create_msti(br, mstid) ? 0 : -1; +} + +int CTL_delete_msti(int br_index, __u16 mstid) +{ + CTL_CHECK_BRIDGE; + return MSTP_IN_delete_msti(br, mstid) ? 0 : -1; +} + +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, char *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; +} diff --git a/brmon.c b/brmon.c new file mode 100644 index 0000000..796d347 --- /dev/null +++ b/brmon.c @@ -0,0 +1,225 @@ +/* + * 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 "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) +{ + FILE *fp = arg; + struct ifinfomsg *ifi = NLMSG_DATA(n); + struct rtattr * tb[IFLA_MAX + 1]; + int len = n->nlmsg_len; + char b1[IFNAMSIZ]; + int af_family = ifi->ifi_family; + bool newlink, up; + int br_index; + + if(n->nlmsg_type == NLMSG_DONE) + return 0; + + len -= NLMSG_LENGTH(sizeof(*ifi)); + if(len < 0) + { + return -1; + } + + 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) + { + fprintf(stderr, "BUG: nil ifname\n"); + return -1; + } + + if(n->nlmsg_type == RTM_DELLINK) + fprintf(fp, "Deleted "); + + fprintf(fp, "%d: %s ", ifi->ifi_index, (char*)RTA_DATA(tb[IFLA_IFNAME])); + + if(tb[IFLA_OPERSTATE]) + { + int state = *(int*)RTA_DATA(tb[IFLA_OPERSTATE]); + switch (state) + { + case IF_OPER_UNKNOWN: + fprintf(fp, "Unknown "); + break; + case IF_OPER_NOTPRESENT: + fprintf(fp, "Not Present "); + break; + case IF_OPER_DOWN: + fprintf(fp, "Down "); + break; + case IF_OPER_LOWERLAYERDOWN: + fprintf(fp, "Lowerlayerdown "); + break; + case IF_OPER_TESTING: + fprintf(fp, "Testing "); + break; + case IF_OPER_DORMANT: + fprintf(fp, "Dormant "); + break; + case IF_OPER_UP: + fprintf(fp, "Up "); + break; + default: + fprintf(fp, "State(%d) ", state); + } + } + + if(tb[IFLA_MTU]) + fprintf(fp, "mtu %u ", *(int*)RTA_DATA(tb[IFLA_MTU])); + + if(tb[IFLA_MASTER]) + { + fprintf(fp, "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) + fprintf(fp, "state %s", port_states[state]); + else + fprintf(fp, "state (%d)", state); + } + + fprintf(fp, "\n"); + fflush(fp); + + newlink = (n->nlmsg_type == RTM_NEWLINK); + up = false; + if(newlink && tb[IFLA_OPERSTATE]) + { + int state = *(uint8_t*)RTA_DATA(tb[IFLA_OPERSTATE]); + up = (state == IF_OPER_UP) || (state == IF_OPER_UNKNOWN); + } + + 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, up); + + return 0; +} + +static inline void br_ev_handler(uint32_t events, struct epoll_event_handler *h) +{ + if(rtnl_listen(&rth, dump_msg, stdout) < 0) + { + fprintf(stderr, "Error on bridge monitoring socket\n"); + exit(-1); + } +} + +int init_bridge_ops(void) +{ + if(rtnl_open(&rth, ~RTMGRP_TC) < 0) + { + fprintf(stderr, "Couldn't open rtnl socket for monitoring\n"); + return -1; + } + + if(rtnl_open(&rth_state, 0) < 0) + { + fprintf(stderr, "Couldn't open rtnl socket for setting state\n"); + return -1; + } + + if(rtnl_wilddump_request(&rth, PF_BRIDGE, RTM_GETLINK) < 0) + { + fprintf(stderr, "Cannot send dump request: %m\n"); + return -1; + } + + if(rtnl_dump_filter(&rth, dump_msg, stdout, NULL, NULL) < 0) + { + fprintf(stderr, "Dump terminated\n"); + return -1; + } + + if(fcntl(rth.fd, F_SETFL, O_NONBLOCK) < 0) + { + fprintf(stderr, "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; +} diff --git a/ctl_functions.h b/ctl_functions.h new file mode 100644 index 0000000..da22dae --- /dev/null +++ b/ctl_functions.h @@ -0,0 +1,490 @@ +/***************************************************************************** + 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 "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 */ +#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, char *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(in->name, 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); + +/* 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); \ + 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 */ diff --git a/ctl_main.c b/ctl_main.c new file mode 100644 index 0000000..863e9a8 --- /dev/null +++ b/ctl_main.c @@ -0,0 +1,1114 @@ +/***************************************************************************** + 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 "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; +} + +#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") + +static int do_showbridge(const char *br_name) +{ + CIST_BridgeStatus s; + char root_port_name[IFNAMSIZ]; + unsigned int root_portno; + 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; + 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 %-10u ", s.root_max_age); + printf("bridge max age %u\n", s.bridge_max_age); + printf(" forward delay %-10u ", s.root_forward_delay); + printf("bridge forward delay %u\n", s.bridge_forward_delay); + printf(" tx hold count %-10u ", s.tx_hold_count); + printf("max hops %hhu\n", s.max_hops); + printf(" force protocol version %u\n", s.protocol_version); + printf(" time since topology change %u\n", s.time_since_topology_change); + printf(" toplogy change count %u\n", s.topology_change_count); + printf(" topology change %s\n", BOOL_STR(s.topology_change)); + + return 0; +} + +#define SYSFS_PATH_MAX 256 +#define SYSFS_CLASS_NET "/sys/class/net" + +static int isbridge(const struct dirent *entry) +{ + char path[SYSFS_PATH_MAX]; + int save_errno; + bool result; + struct stat st; + + snprintf(path, SYSFS_PATH_MAX, SYSFS_CLASS_NET "/%s/bridge", + entry->d_name); + save_errno = errno; + result = (0 == stat(path, &st)) && S_ISDIR(st.st_mode); + errno = save_errno; + return result; +} + +static int cmd_showbridge(int argc, char *const *argv) +{ + int i, count = 0; + int r = 0; + struct dirent **namelist; + + if(1 < argc) + { + count = argc - 1; + } + else + { + /* TODO: use versionsort, if available */ + count = scandir(SYSFS_CLASS_NET, &namelist, isbridge, alphasort); + if(0 > count) + { + fprintf(stderr, "Error getting list of all bridges\n"); + return -1; + } + } + + for(i = 0; i < count; ++i) + { + const char *name; + if(1 < argc) + name = argv[i + 1]; + else + name = namelist[i]->d_name; + + int err = do_showbridge(name); + if(err) + r = err; + } + + if(1 >= argc) + { + for(i = 0; i < count; ++i) + free(namelist[i]); + free(namelist); + } + + return r; +} + +static int cmd_showtree(int argc, char *const *argv) +{ + MSTI_BridgeStatus s; + char root_port_name[IFNAMSIZ]; + unsigned int root_portno; + 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; + + printf("%s MSTI %hu info\n", argv[1], 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(" toplogy change count %u\n", s.topology_change_count); + printf(" topology change %s\n", BOOL_STR(s.topology_change)); + + return 0; +} + +#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 int detail = 0; + +static int do_showport(int br_index, const char *bridge_name, + const char *port_name) +{ + 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; + } + + 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("toplogy 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 %-23u ", s.port_hello_time); + printf("disputed %s\n", BOOL_STR(s.disputed)); + } + 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)); + } + return 0; +} + +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 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; + + if(2 < argc) + { + count = argc - 2; + } + else + { + char buf[SYSFS_PATH_MAX]; + snprintf(buf, sizeof(buf), SYSFS_CLASS_NET "/%s/brif", argv[1]); + /* TODO: use versionsort, if available */ + count = scandir(buf, &namelist, not_dot_dotdot, alphasort); + if(0 > count) + { + fprintf(stderr, "Error getting list of all ports of bridge %s\n", + argv[1]); + return -1; + } + } + + for(i = 0; i < count; ++i) + { + const char *name; + if(2 < argc) + name = argv[i + 2]; + else + name = namelist[i]->d_name; + + int err = do_showport(br_index, argv[1], name); + if(err) + r = err; + } + + if(2 >= argc) + { + for(i = 0; i < count; ++i) + free(namelist[i]); + free(namelist); + } + + return r; +} + +static int cmd_showportdetail(int argc, char *const *argv) +{ + detail = 1; + return cmd_showport(argc, argv); +} + +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; + + printf("%s:%s MSTI %hu info\n", argv[1], argv[2], 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 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, 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; + return set_bridge_cfg(bridge_max_age, getuint(argv[2])); +} + +static int cmd_setbridgefdelay(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_forward_delay, getuint(argv[2])); +} + +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_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_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 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, i; + __u16 mstids[MAX_IMPLEMENTATION_MSTIS + 1]; /* +1 - for the CIST */ + + if(CTL_get_mstilist(br_index, &num_mstis, mstids)) + return -1; + + printf("%s list of known MSTIs:\n", argv[1]); + for(i = 0; i < num_mstis; ++i) + printf(" %hu", mstids[i]); + printf("\n"); + + 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; + int i; + + if(CTL_get_mstconfid(br_index, &cfgid)) + return -1; + + printf("%s MST Configuration Identifier:\n", argv[1]); + 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 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 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; + + printf("%s VID-to-FID allocation table:\n", argv[1]); + int i, cur_fid; + int interval_count; + vid2fid[MAX_VID + 1] = 0xFFFF; /* helps to finalize last interval */ + do{ + cur_fid = vid2fid[0]; + for(i = 1; i <= MAX_VID; ++i) + if(cur_fid > vid2fid[i]) + cur_fid = vid2fid[i]; + if(cur_fid > MAX_FID) + break; + printf(" FID %u:", cur_fid); + for(i = 0, interval_count = 0; i <= (MAX_VID + 1); ++i) + { + if(cur_fid != vid2fid[i]) + { + if(interval_count) + { + printf(" %u", i - interval_count); + if(1 < interval_count) + printf("-%u", i - 1); + interval_count = 0; + } + continue; + } + vid2fid[i] = 0xFFFF; + ++interval_count; + } + printf("\n"); + }while(true); + + 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; + + printf("%s FID-to-MSTID allocation table:\n", argv[1]); + int i, cur_mstid; + int interval_count; + fid2mstid[MAX_FID + 1] = 0xFFFF; /* helps to finalize last interval */ + do{ + 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; + printf(" MSTID %u:", cur_mstid); + for(i = 0, interval_count = 0; i <= (MAX_FID + 1); ++i) + { + if(cur_mstid != fid2mstid[i]) + { + if(interval_count) + { + printf(" %u", i - interval_count); + if(1 < interval_count) + printf("-%u", i - 1); + interval_count = 0; + } + continue; + } + fid2mstid[i] = 0xFFFF; + ++interval_count; + } + printf("\n"); + }while(true); + + 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; + int vid = get_id(argv[2], "VID", MAX_VID); + if(0 > vid) + return vid; + if(0 == vid) + { + fprintf(stderr, "Bad VID %s\n", argv[2]); + return -1; + } + int fid = get_id(argv[3], "FID", MAX_FID); + if(0 > fid) + return fid; + return CTL_set_vid2fid(br_index, vid, fid); +} + +static int cmd_setfid2mstid(int argc, char *const *argv) +{ + int br_index = get_index(argv[1], "bridge"); + if(0 > br_index) + return br_index; + int fid = get_id(argv[2], "FID", MAX_FID); + if(0 > fid) + return fid; + int mstid = get_id(argv[3], "mstid", MAX_MSTID); + if(0 > mstid) + return mstid; + return CTL_set_fid2mstid(br_index, fid, mstid); +} + +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[] = +{ + /* Show global bridge */ + {0, 32, "showbridge", cmd_showbridge, + "[ ... ]", "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, + " [ ... ]", "Show port state for the CIST"}, + {1, 32, "showportdetail", cmd_showportdetail, + " [ ... ]", "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"}, + {3, 0, "setvid2fid", cmd_setvid2fid, + " ", "Set VID-to-FID allocation"}, + {3, 0, "setfid2mstid", cmd_setfid2mstid, + " ", "Set FID-to-MSTID 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, "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"}, + /* 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)"}, + + /* 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) + { + printf("-%s:\n %-16s %s\n", commands[i].help, commands[i].name, + commands[i].format); + } +} + +static void help() +{ + printf("Usage: mstpctl [commands]\n"); + printf("commands:\n"); + command_helpall(); +} + +#define PACKAGE_VERSION2(v, b) "mstp, " #v "-" #b +#define PACKAGE_VERSION(v, b) PACKAGE_VERSION2(v, b) + +int main(int argc, char *const *argv) +{ + const struct command *cmd; + int f; + static const struct option options[] = + { + {.name = "help", .val = 'h'}, + {.name = "version", .val = 'V'}, + {0} + }; + + while(EOF != (f = getopt_long(argc, argv, "Vh", options, NULL))) + switch(f) + { + case 'h': + help(); + return 0; + case 'V': + printf("%s\n", PACKAGE_VERSION(VERSION, BUILD)); + return 0; + default: + fprintf(stderr, "Unknown option '%c'\n", f); + goto help; + } + + if(argc == optind) + goto help; + + if(ctl_client_init()) + { + fprintf(stderr, "can't setup control connection\n"); + return 1; + } + + argc -= optind; + argv += optind; + if(NULL == (cmd = command_lookup(argv[0]))) + { + fprintf(stderr, "never heard of command [%s]\n", argv[0]); + goto help; + } + + if(argc < cmd->nargs + 1 || argc > cmd->nargs + cmd->optargs + 1) + { + printf("Incorrect number of arguments for command\n"); + printf("Usage: mstpctl %s %s\n %s\n", + cmd->name, cmd->format, cmd->help); + 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) + +/*********************** 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); +} diff --git a/ctl_socket_client.c b/ctl_socket_client.c new file mode 100644 index 0000000..05f9620 --- /dev/null +++ b/ctl_socket_client.c @@ -0,0 +1,177 @@ +/***************************************************************************** + 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; +} diff --git a/ctl_socket_client.h b/ctl_socket_client.h new file mode 100644 index 0000000..ef482c2 --- /dev/null +++ b/ctl_socket_client.h @@ -0,0 +1,37 @@ +/***************************************************************************** + 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 */ diff --git a/ctl_socket_server.c b/ctl_socket_server.c new file mode 100644 index 0000000..27f01a6 --- /dev/null +++ b/ctl_socket_server.c @@ -0,0 +1,196 @@ +/***************************************************************************** + 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; + + 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); + + 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); + + 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 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]; + int l; + + msg.msg_name = &sa; + msg.msg_namelen = sizeof(sa); + msg.msg_iov = iov; + msg.msg_iovlen = 3; + msg.msg_control = NULL; + msg.msg_controllen = 0; + 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; + } + + msg_log_offset = 0; + ctl_in_handler = 1; + + mhdr.res = handle_message(mhdr.cmd, msg_inbuf, mhdr.lin, + msg_outbuf, mhdr.lout); + + 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); + } +} + +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); +} diff --git a/ctl_socket_server.h b/ctl_socket_server.h new file mode 100644 index 0000000..944e5ef --- /dev/null +++ b/ctl_socket_server.h @@ -0,0 +1,38 @@ +/***************************************************************************** + 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 */ diff --git a/epoll_loop.c b/epoll_loop.c new file mode 100644 index 0000000..e2c3442 --- /dev/null +++ b/epoll_loop.c @@ -0,0 +1,148 @@ +/***************************************************************************** + 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 "epoll_loop.h" +#include "bridge_ctl.h" + +/* globals */ +static int epoll_fd = -1; +static struct timeval nexttimeout; + +int init_epoll(void) +{ + int r = epoll_create(128); + if(r < 0) + { + fprintf(stderr, "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) + { + fprintf(stderr, "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) + { + fprintf(stderr, "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 timeval *second, struct timeval *first) +{ + return (second->tv_sec - first->tv_sec) * 1000 + + (second->tv_usec - first->tv_usec) / 1000; +} + +static inline void run_timeouts(void) +{ + bridge_one_second(); + ++(nexttimeout.tv_sec); +} + +int epoll_main_loop(void) +{ + gettimeofday(&nexttimeout, NULL); + ++(nexttimeout.tv_sec); +#define EV_SIZE 8 + struct epoll_event ev[EV_SIZE]; + + while(1) + { + int r, i; + int timeout; + + struct timeval tv; + gettimeofday(&tv, NULL); + timeout = time_diff(&nexttimeout, &tv); + if(timeout < 0) + { + run_timeouts(); + timeout = 0; + } + + r = epoll_wait(epoll_fd, ev, EV_SIZE, timeout); + if(r < 0 && errno != EINTR) + { + fprintf(stderr, "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; +} diff --git a/epoll_loop.h b/epoll_loop.h new file mode 100644 index 0000000..54f6a4d --- /dev/null +++ b/epoll_loop.h @@ -0,0 +1,53 @@ +/***************************************************************************** + 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(void); + +int add_epoll(struct epoll_event_handler *h); + +int remove_epoll(struct epoll_event_handler *h); + +#endif /* EPOLL_LOOP_H */ diff --git a/hmac_md5.c b/hmac_md5.c new file mode 100644 index 0000000..bd2b249 --- /dev/null +++ b/hmac_md5.c @@ -0,0 +1,534 @@ +/* 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(output, input, len) +unsigned char *output; +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(output, input, len) +UINT4 *output; +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(state, block) +UINT4 state[4]; +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(context) +MD5_CTX *context; /* 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(context, input, inputLen) +MD5_CTX *context; /* context */ +unsigned char *input; /* input block */ +unsigned int inputLen; /* length of input block */ +{ + 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(digest, context) +unsigned char digest[16]; /* message digest */ +MD5_CTX *context; /* 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(text, text_len, key, key_len, digest) +unsigned char* text; /* pointer to data stream */ +int text_len; /* length of data stream */ +unsigned char* key; /* pointer to authentication key */ +int key_len; /* length of authentication key */ +caddr_t digest; /* caller digest to be filled in */ +{ + 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(string, digest) +char *string; +caddr_t digest; /* caller digest to be filled in */ +{ + MD5_CTX context; + unsigned int len = strlen(string); + + MD5Init(&context); + MD5Update(&context, 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("Hi There", 8, key, 16, 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("what do ya want for nothing?", 28, "Jefe", 4, 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, 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, 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, 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, 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 */ diff --git a/libnetlink.c b/libnetlink.c new file mode 100644 index 0000000..42238f9 --- /dev/null +++ b/libnetlink.c @@ -0,0 +1,632 @@ +/* + * 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 "libnetlink.h" + +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 = 32768; + int rcvbuf = 32768; + + memset(rth, 0, sizeof(*rth)); + + rth->fd = socket(AF_NETLINK, SOCK_RAW, protocol); + if (rth->fd < 0) { + perror("Cannot open netlink socket"); + return -1; + } + + if (setsockopt(rth->fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) + < 0) { + perror("SO_SNDBUF"); + return -1; + } + + if (setsockopt(rth->fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) + < 0) { + perror("SO_RCVBUF"); + return -1; + } + + memset(&rth->local, 0, sizeof(rth->local)); + rth->local.nl_family = AF_NETLINK; + rth->local.nl_groups = subscriptions; + + if (bind(rth->fd, (struct sockaddr *)&rth->local, sizeof(rth->local)) < + 0) { + perror("Cannot bind netlink socket"); + return -1; + } + addr_len = sizeof(rth->local); + if (getsockname(rth->fd, (struct sockaddr *)&rth->local, &addr_len) < 0) { + perror("Cannot getsockname"); + return -1; + } + if (addr_len != sizeof(rth->local)) { + fprintf(stderr, "Wrong address length %d\n", addr_len); + return -1; + } + if (rth->local.nl_family != AF_NETLINK) { + fprintf(stderr, "Wrong address family %d\n", + rth->local.nl_family); + return -1; + } + rth->seq = time(NULL); + return 0; +} + +int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions) +{ + return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE); +} + +int rtnl_wilddump_request(struct rtnl_handle *rth, int family, int type) +{ + struct { + struct nlmsghdr nlh; + struct rtgenmsg g; + } req; + 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; + perror("OVERRUN"); + continue; + } + + if (status == 0) { + fprintf(stderr, "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))) { + fprintf(stderr, "ERROR truncated\n"); + } else { + errno = -err->error; + perror("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) { + fprintf(stderr, "Message truncated\n"); + continue; + } + if (status) { + fprintf(stderr, "!!!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) { + perror("Cannot talk to rtnetlink"); + return -1; + } + + memset(buf, 0, sizeof(buf)); + + iov.iov_base = buf; + + while (1) { + iov.iov_len = sizeof(buf); + status = recvmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR) + continue; + perror("OVERRUN"); + continue; + } + if (status == 0) { + fprintf(stderr, "EOF on netlink\n"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + fprintf(stderr, "sender address length == %d\n", + msg.msg_namelen); + 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) { + fprintf(stderr, "Truncated message\n"); + return -1; + } + fprintf(stderr, + "!!!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)) { + fprintf(stderr, "ERROR truncated\n"); + } else { + errno = -err->error; + if (errno == 0) { + if (answer) + memcpy(answer, h, + h->nlmsg_len); + return 0; + } + perror("RTNETLINK answers"); + } + return -1; + } + if (answer) { + memcpy(answer, h, h->nlmsg_len); + return 0; + } + + fprintf(stderr, "Unexpected reply!!!\n"); + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "Message truncated\n"); + continue; + } + if (status) { + fprintf(stderr, "!!!Remnant of size %d\n", status); + 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; + perror("OVERRUN"); + return -1; + } + if (status == 0) { + fprintf(stderr, "EOF on netlink\n"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + fprintf(stderr, "Sender address length == %d\n", + msg.msg_namelen); + 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) { + fprintf(stderr, "Truncated message\n"); + return -1; + } + fprintf(stderr, + "!!!malformed message: len=%d\n", len); + return -1; + } + + err = handler(&nladdr, h, jarg); + if (err < 0) { + fprintf(stderr, "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) { + fprintf(stderr, "Message truncated\n"); + continue; + } + if (status) { + fprintf(stderr, "!!!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, type; + int l; + + status = fread(&buf, 1, sizeof(*h), rtnl); + + if (status < 0) { + if (errno == EINTR) + continue; + perror("rtnl_from_file: fread"); + return -1; + } + if (status == 0) + return 0; + + len = h->nlmsg_len; + type = h->nlmsg_type; + l = len - sizeof(*h); + + if (l < 0 || len > sizeof(buf)) { + fprintf(stderr, "!!!malformed message: len=%d @%lu\n", + len, ftell(rtnl)); + return -1; + } + + status = fread(NLMSG_DATA(h), 1, NLMSG_ALIGN(l), rtnl); + + if (status < 0) { + perror("rtnl_from_file: fread"); + return -1; + } + if (status < l) { + fprintf(stderr, "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) { + fprintf(stderr, + "addattr32: Error! max allowed bound %d exceeded\n", + maxlen); + return -1; + } + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), &data, 4); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + return 0; +} + +int 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) { + fprintf(stderr, + "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) { + fprintf(stderr, + "addattr_l ERROR: message exceeded bound of %d\n", + maxlen); + return -1; + } + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + return 0; +} + +int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len) +{ + if (NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len) > maxlen) { + fprintf(stderr, + "addraw_l ERROR: message exceeded bound of %d\n", + maxlen); + return -1; + } + + memcpy(NLMSG_TAIL(n), data, len); + memset((void *)NLMSG_TAIL(n) + len, 0, NLMSG_ALIGN(len) - len); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len); + return 0; +} + +int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *subrta; + + if (RTA_ALIGN(rta->rta_len) + len > maxlen) { + fprintf(stderr, + "rta_addattr32: Error! max allowed bound %d exceeded\n", + maxlen); + return -1; + } + subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), &data, 4); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len; + return 0; +} + +int rta_addattr_l(struct rtattr *rta, int maxlen, int type, + const void *data, int alen) +{ + struct rtattr *subrta; + int len = RTA_LENGTH(alen); + + if (RTA_ALIGN(rta->rta_len) + RTA_ALIGN(len) > maxlen) { + fprintf(stderr, + "rta_addattr_l: Error! max allowed bound %d exceeded\n", + maxlen); + return -1; + } + subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), data, alen); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + RTA_ALIGN(len); + return 0; +} + +int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) +{ + memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); + while (RTA_OK(rta, len)) { + if (rta->rta_type <= max) + tb[rta->rta_type] = rta; + rta = RTA_NEXT(rta, len); + } + if (len) + fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", len, + rta->rta_len); + return 0; +} + +int parse_rtattr_byindex(struct rtattr *tb[], int max, struct rtattr *rta, + int len) +{ + int i = 0; + + memset(tb, 0, sizeof(struct rtattr *) * max); + while (RTA_OK(rta, len)) { + if (rta->rta_type <= max && i < max) + tb[i++] = rta; + rta = RTA_NEXT(rta, len); + } + if (len) + fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", len, + rta->rta_len); + return i; +} diff --git a/libnetlink.h b/libnetlink.h new file mode 100644 index 0000000..2ebb9a7 --- /dev/null +++ b/libnetlink.h @@ -0,0 +1,55 @@ +#ifndef __LIBNETLINK_H__ +#define __LIBNETLINK_H__ + +#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_addattr32(struct rtattr *rta, int maxlen, int type, __u32 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); + +#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__ */ diff --git a/list.h b/list.h new file mode 100644 index 0000000..e404c7b --- /dev/null +++ b/list.h @@ -0,0 +1,519 @@ +/** + * + * 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 diff --git a/log.h b/log.h new file mode 100644 index 0000000..2bf0fb8 --- /dev/null +++ b/log.h @@ -0,0 +1,91 @@ +/***************************************************************************** + 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 */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..7af48a2 --- /dev/null +++ b/main.c @@ -0,0 +1,287 @@ +/***************************************************************************** + 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 "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" + +#define APP_NAME "mstpd" + +static int become_daemon = 1; +static int is_daemon = 0; +int log_level = LOG_LEVEL_DEFAULT; + +#ifdef MISC_TEST_FUNCS +static bool test_ports_trees_mesh(void); +#endif /* MISC_TEST_FUNCS */ + +int main(int argc, char *argv[]) +{ + int c; + + /* Sanity check */ + { + 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"); + } + + while((c = getopt(argc, argv, "dv:")) != -1) + { + switch (c) + { + case 'd': + become_daemon = 0; + 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; + } + default: + return -1; + } + } + + if(become_daemon) + { + FILE *f = fopen("/var/run/"APP_NAME".pid", "w"); + if(!f) + { + ERROR("can't open /var/run/"APP_NAME".pid"); + return -1; + } + openlog(APP_NAME, 0, LOG_DAEMON); + if(daemon(0, 0)) + { + ERROR("can't daemonize"); + return -1; + } + is_daemon = 1; + fprintf(f, "%d", getpid()); + fclose(f); + } + + 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); + + return epoll_main_loop(); +} + +/*********************** Logging *********************/ + +#include +#include + +void vDprintf(int level, const char *fmt, va_list ap) +{ + if(level > log_level) + return; + + if(!is_daemon) + { + 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); + + list_del(&prt[1]->br_list); + MSTP_IN_delete_port(prt[1]); + if(!MSTP_IN_delete_msti(br, 0xE12)) + goto error_exit; + list_del(&prt[3]->br_list); + 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 */ diff --git a/mstp.c b/mstp.c new file mode 100644 index 0000000..0ab4886 --- /dev/null +++ b/mstp.c @@ -0,0 +1,4392 @@ +/* + * 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. + * Maybe 802.1Q-2011 clarifies this, but I don't have the text. + */ + +/* 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 ;) + */ + +#include +#include +#include + +#include "mstp.h" +#include "log.h" + +static void PTSM_tick(port_t *prt); +static void TCSM_run(per_tree_port_t *ptp); +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); + +#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) + +/* + * 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), + 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 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); + assign(tree->BridgeTimes.remainingHops, br->MaxHops); /* 13.23.4 */ + assign(tree->BridgeTimes.Forward_Delay, br->Forward_Delay); /* 13.23.4 */ + assign(tree->BridgeTimes.Max_Age, br->Max_Age); /* 13.23.4 */ + /* 13.23.4 */ + assign(tree->BridgeTimes.Message_Age, __constant_cpu_to_be16(0)); + /* 17.14 of 802.1D */ + assign(tree->BridgeTimes.Hello_Time, __constant_cpu_to_be16(2)); + + /* 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 */ + + /* The following are initialized in BEGIN state: + * - rootPortId, rootPriority, rootTimes: in Port Role Selection SM + */ + 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; + assign(ptp->rcvdTc, boolFalse); + assign(ptp->tcProp, boolFalse); + assign(ptp->updtInfo, boolFalse); + /* 0x80 = default port priority (17.14 of 802.1D) */ + ptp->portId = __constant_cpu_to_be16(0x8000) | prt->port_number; + assign(ptp->master, boolFalse); /* 13.24.5 */ + 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); + + /* 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 + */ + + /* The following are not initialized (set to zero thanks to calloc): + * - disputed + * - rcvdInfo + * - mastered + * - msgPriority + * - msgTimes + */ + return ptp; +} + +/* External events */ + +bool MSTP_IN_bridge_create(bridge_t *br, __u8 *macaddr) +{ + tree_t *cist; + + /* Initialize all fields except sysdeps and anchor */ + INIT_LIST_HEAD(&br->ports); + INIT_LIST_HEAD(&br->trees); + assign(br->bridgeEnabled, boolFalse); + memset(br->vid2fid, 0, sizeof(br->vid2fid)); + memset(br->fid2mstid, 0, sizeof(br->fid2mstid)); + assign(br->MstConfigId.s.selector, (__u8)0); + sprintf(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 */ + assign(br->MaxHops, (__u8)20); /* 13.37.3 */ + assign(br->ForceProtocolVersion, protoMSTP); + assign(br->Forward_Delay, __constant_cpu_to_be16(15)); /* 17.14 of 802.1D */ + assign(br->Max_Age, __constant_cpu_to_be16(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->rapidAgeingWhile, 0u); + + br->uptime = 0; + + /* Create CIST */ + if(!(cist = create_tree(br, macaddr, 0))) + return false; + list_add_tail(&cist->bridge_list, &br->trees); + + br_state_machines_begin(br); + 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; + + /* 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; + assign(prt->operPointToPointMAC, boolFalse); + assign(prt->portEnabled, boolFalse); + assign(prt->infoInternal, boolFalse); + assign(prt->rcvdInternal, boolFalse); + assign(prt->rcvdTcAck, boolFalse); + assign(prt->rcvdTcn, boolFalse); + assign(prt->restrictedRole, boolFalse); /* 13.25.14 */ + assign(prt->restrictedTcn, boolFalse); /* 13.25.15 */ + assign(prt->ExternalPortPathCost, MAX_PATH_COST); /* 13.37.1 */ + assign(prt->AdminEdgePort, boolFalse); /* 13.25 */ + assign(prt->AutoEdge, boolTrue); /* 13.25 */ + + /* 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 + */ + + /* 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; + + if(prt->portEnabled) + { + assign(prt->portEnabled, boolFalse); + 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); + } + + br_state_machines_run(br); +} + +void MSTP_IN_delete_bridge(bridge_t *br) +{ + tree_t *tree, *nxt_tree; + port_t *prt, *nxt_prt; + + assign(br->bridgeEnabled, boolFalse); + + /* 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) + { + list_del(&prt->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) +{ + if(br->bridgeEnabled == up) + return; + assign(br->bridgeEnabled, up); + 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) + { + assign(prt->operPointToPointMAC, new_p2p); + changed = true; + } + + if(!prt->portEnabled) + { + assign(prt->portEnabled, boolTrue); + changed = true; + } + } + else + { + if(prt->portEnabled) + { + assign(prt->portEnabled, boolFalse); + 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(br->rapidAgeingWhile) + { + if((--(br->rapidAgeingWhile)) == 0) + MSTP_OUT_set_ageing_time(br, -1); + } + + br_state_machines_run(br); +} + +void MSTP_IN_all_fids_flushed(per_tree_port_t *ptp) +{ + bridge_t *br = ptp->port->bridge; + if(!br->bridgeEnabled) + return; + assign(ptp->fdbFlush, boolFalse); + TCSM_run(ptp); + 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; + + 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"); + 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"); + 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 with %d MSTIs", + prt->rcvdBpduNumOfMstis); + break; + default: + goto bpdu_validation_failed; + } + + assign(prt->rcvdBpduData, *bpdu); + assign(prt->rcvdBpdu, boolTrue); + + 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); + assign(status->topology_change, cist->topology_change); + 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); + status->root_max_age = __be16_to_cpu(cist->rootTimes.Max_Age); + status->root_forward_delay = __be16_to_cpu(cist->rootTimes.Forward_Delay); + status->bridge_max_age = __be16_to_cpu(br->Max_Age); + status->bridge_forward_delay = __be16_to_cpu(br->Forward_Delay); + status->max_hops = br->MaxHops; + assign(status->tx_hold_count, br->Transmit_Hold_Count); + assign(status->protocol_version, br->ForceProtocolVersion); + assign(status->enabled, br->bridgeEnabled); +} + +/* 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); + assign(status->topology_change, tree->topology_change); + 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; + unsigned int new_forward_delay, new_max_age; + __be16 valueMaxAge, valueForwardDelay; + 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"); + r = -1; + } + } + else + new_max_age = __be16_to_cpu(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"); + r = -1; + } + } + else + new_forward_delay = __be16_to_cpu(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) >= 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\n"); + r = -1; + } + } + + if(cfg->set_max_hops) + { + if((6 > cfg->max_hops) || (40 < cfg->max_hops)) + { + ERROR_BRNAME(br, "Bridge Max Hops must be between 6 and 40"); + r = -1; + } + } + + if(r) + return r; + + /* Secondly, do set */ + changed = changedBridgeTimes = init = false; + + if(cfg->set_bridge_max_age || cfg->set_bridge_forward_delay) + { + valueMaxAge = __cpu_to_be16(new_max_age); + valueForwardDelay = __cpu_to_be16(new_forward_delay); + if(cmp(valueMaxAge, !=, br->Max_Age) + || cmp(valueForwardDelay, !=, br->Forward_Delay) + ) + { + assign(br->Max_Age, valueMaxAge); + assign(br->Forward_Delay, valueForwardDelay); + 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; + } + } + + /* 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); + /* 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) + { + assign(ptp->selected, boolFalse); + assign(ptp->reselect, boolTrue); + } + } + } + + 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) + { + assign(ptp->selected, boolFalse); + assign(ptp->reselect, boolTrue); + } + 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)); + assign(status->tc_ack, prt->tcAck); + status->port_hello_time = __be16_to_cpu(cist->portTimes.Hello_Time); + assign(status->admin_edge_port, prt->AdminEdgePort); + assign(status->auto_edge_port, prt->AutoEdge); + assign(status->oper_edge_port, prt->operEdge); + assign(status->enabled, prt->portEnabled); + assign(status->admin_p2p, prt->AdminP2P); + assign(status->oper_p2p, prt->operPointToPointMAC); + assign(status->restricted_role, prt->restrictedRole); + assign(status->restricted_tcn, prt->restrictedTcn); + assign(status->role, cist->role); + assign(status->disputed, cist->disputed); + assign(status->admin_internal_port_path_cost, + cist->AdminInternalPortPathCost); + assign(status->internal_port_path_cost, cist->InternalPortPathCost); +} + +/* 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); + assign(status->role, ptp->role); + assign(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; + + /* Firstly, validation */ + if(cfg->set_admin_p2p) + { + switch(cfg->admin_p2p) + { + case p2pAuto: + case p2pForceTrue: + case p2pForceFalse: + break; + default: + cfg->admin_p2p = p2pAuto; + } + } + + /* 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); + assign(cist->selected, boolFalse); + assign(cist->reselect, boolTrue); + } + } + + 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) + { + assign(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(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; + } + 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 */ + assign(ptp->selected, boolFalse); + assign(ptp->reselect, boolTrue); + + 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; + + if(rstpVersion(br) && prt->portEnabled && br->bridgeEnabled) + { + assign(prt->mcheck, boolTrue); + 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; + + for(vid = 1; vid <= MAX_VID; ++vid) + if(vids2fids[vid] > MAX_FID) + { + ERROR_BRNAME(br, "Error allocating VID(%hu) to FID(%hu)", + vid, vids2fids[vid]); + return false; + } + + vid2mstid_changed = false; + for(vid = 1; vid <= MAX_VID; ++vid) + { + if(br->fid2mstid[vids2fids[vid]] != br->fid2mstid[br->vid2fid[vid]]) + { + vid2mstid_changed = true; + break; + } + } + 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) + { + 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(name, 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(br->MstConfigId.s.configuration_name, name, + sizeof(br->MstConfigId.s.configuration_name)); + 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) +{ + 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); + 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 void clearAllRcvdMsgs(port_t *prt) +{ + per_tree_port_t *ptp; + + FOREACH_PTP_IN_PORT(ptp, prt) + assign(ptp->rcvdMsg, boolFalse); +} + +/* 13.26.3 clearReselectTree */ +static void clearReselectTree(tree_t *tree) +{ + per_tree_port_t *ptp; + + FOREACH_PTP_IN_TREE(ptp, tree) + assign(ptp->reselect, boolFalse); +} + +/* 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); + unsigned int HelloTime = __be16_to_cpu(cist->portTimes.Hello_Time); + + assign(ptp->tcWhile, HelloTime + 1); + set_TopologyChange(tree, true); + + if(0 == ptp->MSTID) + assign(prt->newInfo, boolTrue); + else + assign(prt->newInfoMsti, boolTrue); + return; + } + + times_t *times = &tree->rootTimes; + + ptp->tcWhile = __be16_to_cpu(times->Max_Age) + + __be16_to_cpu(times->Forward_Delay); + set_TopologyChange(tree, true); +} + +/* 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) + { + assign(prt->rcvdTcn, boolTrue); + FOREACH_PTP_IN_PORT(ptp_1, prt) + assign(ptp_1->rcvdTc, boolTrue); + return OtherInfo; + } + + if(0 == ptp->MSTID) + { /* CIST */ + switch(BPDU_FLAGS_ROLE_GET(b->flags)) + { + case encodedRoleAlternateBackup: + case encodedRoleRoot: + roleIsDesignated = false; + break; + case encodedRoleDesignated: + roleIsDesignated = true; + break; + default: + return OtherInfo; + } + cist = true; + + assign(mPri->RRootID, b->cistRRootID); + assign(mPri->DesignatedPortID, b->cistPortID); + assign(mPri->RootID, b->cistRootID); + assign(mPri->ExtRootPathCost, b->cistExtRootPathCost); + /* messageTimes */ + assign(mTimes->Forward_Delay, b->ForwardDelay); + assign(mTimes->Max_Age, b->MaxAge); + assign(mTimes->Message_Age, b->MessageAge); + assign(mTimes->Hello_Time, b->HelloTime); + if(protoMSTP > b->protocolVersion) + { /* STP or RSTP Configuration BPDU */ + /* 13.26.6.NOTE: A Configuration BPDU implicitly conveys a + * Designated Port Role */ + roleIsDesignated = true; + assign(mPri->IntRootPathCost, 0u); + 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)) + ) + { + assign(ptp->agreed, boolTrue); + assign(ptp->proposing, boolFalse); + } + else + assign(ptp->agreed, boolFalse); + cist_agreed = ptp->agreed; + cist_proposing = ptp->proposing; + if(!prt->rcvdInternal) + list_for_each_entry_continue(ptp, &prt->trees, port_list) + { + assign(ptp->agreed, cist_agreed); + assign(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)) + ) + { + assign(ptp->agreed, boolTrue); + assign(ptp->proposing, boolFalse); + } + else + assign(ptp->agreed, boolFalse); +} + +/* 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 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 ;) + * Maybe 802.1Q-2011 clarifies this, but I don't have the text. + */ + if(prt->rcvdBpduData.flags & (1 << offsetLearnig)) + { + assign(ptp->disputed, boolTrue); + assign(ptp->agreed, boolFalse); + if(!prt->rcvdInternal) + list_for_each_entry_continue(ptp, &prt->trees, port_list) + { + assign(ptp->disputed, boolTrue); + assign(ptp->agreed, boolFalse); + } + } + return; + } + /* MSTI */ + if(ptp->rcvdMstiConfig->flags & (1 << offsetLearnig)) + { + assign(ptp->disputed, boolTrue); + assign(ptp->agreed, boolFalse); + } +} + +/* 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) + assign(ptp->mastered, boolFalse); + 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)) + assign(ptp->proposed, boolTrue); + cist_proposed = ptp->proposed; + if(!prt->rcvdInternal) + list_for_each_entry_continue(ptp, &prt->trees, port_list) + assign(ptp->proposed, cist_proposed); + return; + } + /* MSTI */ + if(ptp->rcvdMstiConfig->flags & (1 << offsetProposal)) + assign(ptp->proposed, boolTrue); +} + +/* 13.26.11 recordTimes */ +static void recordTimes(per_tree_port_t *ptp) +{ + assign(ptp->portTimes, ptp->msgTimes); + if(__be16_to_cpu(ptp->portTimes.Hello_Time) < MIN_COMPAT_HELLO_TIME) + assign(ptp->portTimes.Hello_Time, + __constant_cpu_to_be16(MIN_COMPAT_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) + { + assign(ptp->fdbFlush, boolFalse); + return; + } + + bridge_t *br = prt->bridge; + + if(rstpVersion(br)) + { + assign(ptp->fdbFlush, boolTrue); + MSTP_OUT_flush_all_fids(ptp); + } + else + { + per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); + unsigned int FwdDelay = + __be16_to_cpu(cist->designatedTimes.Forward_Delay); + /* Initiate rapid ageing */ + MSTP_OUT_set_ageing_time(br, FwdDelay); + assign(br->rapidAgeingWhile, FwdDelay); + assign(ptp->fdbFlush, boolFalse); + } +} + +/* 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) + assign(ptp->reRoot, boolTrue); +} + +/* 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) + assign(ptp->selected, boolTrue); +} + +/* 13.26.15 setSyncTree */ +static void setSyncTree(tree_t *tree) +{ + per_tree_port_t *ptp; + + FOREACH_PTP_IN_TREE(ptp, tree) + assign(ptp->sync, boolTrue); +} + +/* 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)) + assign(prt->rcvdTcAck, boolTrue); + if(cistFlags & (1 << offsetTc)) + { + assign(ptp->rcvdTc, boolTrue); + if(!prt->rcvdInternal) + list_for_each_entry_continue(ptp, &prt->trees, port_list) + assign(ptp->proposed, boolTrue); + } + return; + } + /* MSTI */ + if(ptp->rcvdMstiConfig->flags & (1 << offsetTc)) + assign(ptp->rcvdTc, boolTrue); +} + +/* 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) + assign(ptp_1->tcProp, boolTrue); + } +} + +/* 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) + { + assign(ptp->agree, boolFalse); + assign(ptp->agreed, boolFalse); + assign(ptp->synced, boolFalse); + assign(ptp->sync, boolTrue); + } + } + } +} + +/* 13.26.19 txConfig */ +static void txConfig(port_t *prt) +{ + bpdu_t b; + per_tree_port_t *cist = GET_CIST_PTP_FROM_PORT(prt); + + 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 ;) + * Maybe 802.1Q-2011 clarifies this, but I don't have the text. + */ + 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); + assign(b.MessageAge, cist->designatedTimes.Message_Age); + assign(b.MaxAge, cist->designatedTimes.Max_Age); + assign(b.HelloTime, cist->portTimes.Hello_Time); /* ! use portTimes ! */ + assign(b.ForwardDelay, cist->designatedTimes.Forward_Delay); + + 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; + } +} + +/* 13.26.20 txMstp */ +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; + + b.protocolIdentifier = 0; + b.bpduType = bpduTypeRST; + /* Standard says "{tcWhile, agree, proposing, role} ... for the Port". + * Which one {tcWhile, agree, proposing, role}? + * I guess that this means {tcWhile, agree, proposing, role} for the CIST. + * But that is only a guess and I could be wrong here ;) + * Maybe 802.1Q-2011 clarifies this, but I don't have the text. + */ + 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); + assign(b.MessageAge, cist->designatedTimes.Message_Age); + assign(b.MaxAge, cist->designatedTimes.Max_Age); + assign(b.HelloTime, cist->portTimes.Hello_Time); /* ! use portTimes ! */ + assign(b.ForwardDelay, cist->designatedTimes.Forward_Delay); + + 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; + + 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) + assign(prt->rcvdRSTP, boolTrue); + else + assign(prt->rcvdSTP, boolTrue); +} + +/* 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 = __be16_to_cpu(cist->portTimes.Message_Age); + unsigned int Max_Age = __be16_to_cpu(cist->portTimes.Max_Age); + unsigned int Hello_Time = __be16_to_cpu(cist->portTimes.Hello_Time); + + if((!prt->rcvdInternal && ((Message_Age + 1) <= Max_Age)) + || (prt->rcvdInternal && (cist->portTimes.remainingHops > 1)) + ) + ptp->rcvdInfoWhile = 3 * Hello_Time; + else + ptp->rcvdInfoWhile = 0; +} + +/* 13.26.24 updtRolesDisabledTree */ +static void updtRolesDisabledTree(tree_t *tree) +{ + per_tree_port_t *ptp; + + FOREACH_PTP_IN_TREE(ptp, tree) + assign(ptp->selectedRole, roleDisabled); +} + +/* 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; + bool cist = (0 == tree->MSTID); + times_t *timesOfRootPort; + + /* 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) + { + assign(root_path_priority.IntRootPathCost, + __cpu_to_be32(__be32_to_cpu(root_path_priority.IntRootPathCost) + + ptp->InternalPortPathCost) + ); + } + else + { + assign(root_path_priority.ExtRootPathCost, + __cpu_to_be32(__be32_to_cpu(root_path_priority.ExtRootPathCost) + + prt->ExternalPortPathCost) + ); + 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) + * So, now I should guess what will work for the timesOfRootPort. + * Below is the result of my guess. I could be wrong, of course: + * timesOfRootPort = root_ptp ? &root_ptp->portTimes + * : &tree->BridgeTimes; + * NOTE: Both Alex Rozin (author of rstplib) and Srinivas Aji (author + * of rstpd) compare portTimes with designatedTimes instead of + * timesOfRootPort. This differs from my interpretation of the standard + * because designatedTimes have incremented Message_Age (or decremented + * remainingHops if rcvdInternal). + */ + + /* 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 + assign(tree->rootTimes.Message_Age, + __cpu_to_be16(__be16_to_cpu(tree->rootTimes.Message_Age) + + 1) + ); + timesOfRootPort = &root_ptp->portTimes; + } + else + { + assign(tree->rootTimes, tree->BridgeTimes); + timesOfRootPort = &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); + } + + /* 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; + + /* f) Set Disabled role */ + if(ioDisabled == ptp->infoIs) + { + assign(ptp->selectedRole, roleDisabled); + continue; + } + + if(!cist && (ioReceived == ptp->infoIs) + && !prt->infoInternal) + { + /* g) Set role for the boundary port in MSTI */ + per_tree_port_t *cist_tree = GET_CIST_PTP_FROM_PORT(prt); + if(roleRoot == cist_tree->selectedRole) + { + assign(ptp->selectedRole, roleMaster); + if(!samePriorityAndTimers(&ptp->portPriority, + &ptp->designatedPriority, + &ptp->portTimes, + timesOfRootPort, + /*cist*/ false)) + assign(ptp->updtInfo, boolTrue); + continue; + } + if(roleAlternate == cist_tree->selectedRole) + { + assign(ptp->selectedRole, roleAlternate); + if(!samePriorityAndTimers(&ptp->portPriority, + &ptp->designatedPriority, + &ptp->portTimes, + timesOfRootPort, + /*cist*/ false)) + assign(ptp->updtInfo, boolTrue); + continue; + } + } + else /* if(cist || (ioReceived != ptp->infoIs) || prt->infoInternal) */ + { + /* h) Set role for the aged info */ + if(ioAged == ptp->infoIs) + { + assign(ptp->selectedRole, roleDesignated); + assign(ptp->updtInfo, boolTrue); + continue; + } + /* i) Set role for the mine info */ + if(ioMine == ptp->infoIs) + { + assign(ptp->selectedRole, roleDesignated); + if(!samePriorityAndTimers(&ptp->portPriority, + &ptp->designatedPriority, + &ptp->portTimes, + timesOfRootPort, + /*cist*/ false)) + assign(ptp->updtInfo, boolTrue); + continue; + } + if(ioReceived == ptp->infoIs) + { + /* j) Set Root role */ + if(root_ptp == ptp) + { + assign(ptp->selectedRole, roleRoot); + assign(ptp->updtInfo, boolFalse); + continue; + } + if(betterorsamePriority(&ptp->portPriority, + &ptp->designatedPriority, + 0, 0, cist)) + { + if(cmp(ptp->portPriority.DesignatedBridgeID, !=, + tree->BridgeIdentifier)) + { + /* k) Set Alternate role */ + assign(ptp->selectedRole, roleAlternate); + } + else + { + /* l) Set Backup role */ + assign(ptp->selectedRole, roleBackup); + } + /* reset updtInfo for both k) and l) */ + assign(ptp->updtInfo, boolFalse); + continue; + } + else /* designatedPriority is better than portPriority */ + { + /* m) Set Designated role */ + assign(ptp->selectedRole, roleDesignated); + assign(ptp->updtInfo, boolTrue); + 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); + + 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); + } + if(ptp->rcvdInfoWhile) + --(ptp->rcvdInfoWhile); + } +} + +/* 13.28 Port Receive state machine */ +#define PRSM_begin(prt) PRSM_to_DISCARD(prt) +static void PRSM_to_DISCARD(port_t *prt/*, bool begin*/) +{ + prt->PRSM_state = PRSM_DISCARD; + + assign(prt->rcvdBpdu, boolFalse); + assign(prt->rcvdRSTP, boolFalse); + assign(prt->rcvdSTP, boolFalse); + clearAllRcvdMsgs(prt); + assign(prt->edgeDelayWhile, prt->bridge->Migrate_Time); + + /* No need to run, no one condition will be met + * if(!begin) + * PRSM_run(prt); */ +} + +static void PRSM_to_RECEIVE(port_t *prt) +{ + prt->PRSM_state = PRSM_RECEIVE; + + updtBPDUVersion(prt); + prt->rcvdInternal = fromSameRegion(prt); + setRcvdMsgs(prt); + assign(prt->operEdge, boolFalse); + assign(prt->rcvdBpdu, boolFalse); + assign(prt->edgeDelayWhile, prt->bridge->Migrate_Time); + + /* No need to run, no one condition will be met + PRSM_run(prt); */ +} + +static void PRSM_run(port_t *prt) +{ + per_tree_port_t *ptp; + bool rcvdAnyMsg; + + if((prt->rcvdBpdu || (prt->edgeDelayWhile != prt->bridge->Migrate_Time)) + && !prt->portEnabled) + { + PRSM_to_DISCARD(prt); + return; + } + + switch(prt->PRSM_state) + { + case PRSM_DISCARD: + if(prt->rcvdBpdu && prt->portEnabled) + PRSM_to_RECEIVE(prt); + return; + case PRSM_RECEIVE: + rcvdAnyMsg = false; + FOREACH_PTP_IN_PORT(ptp, prt) + { + if(ptp->rcvdMsg) + { + rcvdAnyMsg = true; + break; + } + } + if(prt->rcvdBpdu && prt->portEnabled && !rcvdAnyMsg) + PRSM_to_RECEIVE(prt); + return; + } +} + +/* 13.29 Port Protocol Migration state machine */ + +static void PPMSM_run(port_t *prt); +#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; + assign(prt->mcheck, boolFalse); + 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); */ +} + +static void PPMSM_to_SELECTING_STP(port_t *prt) +{ + prt->PPMSM_state = PPMSM_SELECTING_STP; + + assign(prt->sendRSTP, boolFalse); + assign(prt->mdelayWhile, prt->bridge->Migrate_Time); + + PPMSM_run(prt); +} + +static void PPMSM_to_SENSING(port_t *prt) +{ + prt->PPMSM_state = PPMSM_SENSING; + + assign(prt->rcvdRSTP, boolFalse); + assign(prt->rcvdSTP, boolFalse); + + PPMSM_run(prt); +} + +static void PPMSM_run(port_t *prt) +{ + bridge_t *br = prt->bridge; + + switch(prt->PPMSM_state) + { + case PPMSM_CHECKING_RSTP: + if((prt->mdelayWhile != br->Migrate_Time) + && !prt->portEnabled) + { + PPMSM_to_CHECKING_RSTP(prt); + return; + } + if(0 == prt->mdelayWhile) + PPMSM_to_SENSING(prt); + return; + case PPMSM_SELECTING_STP: + if(0 == prt->mdelayWhile || !prt->portEnabled || prt->mcheck) + PPMSM_to_SENSING(prt); + return; + case PPMSM_SENSING: + if(!prt->portEnabled || prt->mcheck + || (rstpVersion(br) && !prt->sendRSTP && prt->rcvdRSTP)) + { + PPMSM_to_CHECKING_RSTP(prt); + return; + } + if(prt->sendRSTP && prt->rcvdSTP) + PPMSM_to_SELECTING_STP(prt); + return; + } +} + +/* 13.30 Bridge Detection state machine */ +static void BDSM_to_EDGE(port_t *prt/*, bool begin*/) +{ + prt->BDSM_state = BDSM_EDGE; + + assign(prt->operEdge, boolTrue); + + /* No need to run, no one condition will be met + * if(!begin) + * BDSM_run(prt); */ +} + +static void BDSM_to_NOT_EDGE(port_t *prt/*, bool begin*/) +{ + prt->BDSM_state = BDSM_NOT_EDGE; + + assign(prt->operEdge, boolFalse); + + /* No need to run, no one condition will be met + * if(!begin) + * BDSM_run(prt); */ +} + +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 void BDSM_run(port_t *prt) +{ + per_tree_port_t *cist; + + switch(prt->BDSM_state) + { + case BDSM_EDGE: + if((!prt->portEnabled && !prt->AdminEdgePort) || !prt->operEdge) + BDSM_to_NOT_EDGE(prt); + return; + case BDSM_NOT_EDGE: + cist = GET_CIST_PTP_FROM_PORT(prt); + /* NOTE: 802.1Q-2005 is not clear, which of the per-tree + * "proposing" flags to use here, or one should combine + * them all for all trees? Maybe 802.1Q-2011 clarifies + * this, but I don't have the text. + * 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) + ) + BDSM_to_EDGE(prt); + return; + } +} + +/* 13.31 Port Transmit state machine */ + +static void PTSM_run(port_t *prt); +#define PTSM_begin(prt) PTSM_to_TRANSMIT_INIT((prt), true) + +static void PTSM_to_TRANSMIT_INIT(port_t *prt, bool begin) +{ + prt->PTSM_state = PTSM_TRANSMIT_INIT; + + assign(prt->newInfo, boolTrue); + assign(prt->newInfoMsti, boolTrue); + assign(prt->txCount, 0u); + + if(!begin && prt->portEnabled) /* prevent infinite loop */ + PTSM_run(prt); +} + +static void PTSM_to_TRANSMIT_CONFIG(port_t *prt) +{ + prt->PTSM_state = PTSM_TRANSMIT_CONFIG; + + assign(prt->newInfo, boolFalse); + txConfig(prt); + ++(prt->txCount); + assign(prt->tcAck, boolFalse); + + PTSM_run(prt); +} + +static void PTSM_to_TRANSMIT_TCN(port_t *prt) +{ + prt->PTSM_state = PTSM_TRANSMIT_TCN; + + assign(prt->newInfo, boolFalse); + txTcn(prt); + ++(prt->txCount); + + PTSM_run(prt); +} + +static void PTSM_to_TRANSMIT_RSTP(port_t *prt) +{ + prt->PTSM_state = PTSM_TRANSMIT_RSTP; + + assign(prt->newInfo, boolFalse); + assign(prt->newInfoMsti, boolFalse); + txMstp(prt); + ++(prt->txCount); + assign(prt->tcAck, boolFalse); + + PTSM_run(prt); +} + +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); +} + +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 = __be16_to_cpu(cist->portTimes.Hello_Time); + + PTSM_run(prt); +} + +static void PTSM_run(port_t *prt) +{ + /* bool allTransmitReady; */ + per_tree_port_t *ptp; + port_role_t cistRole; + bool mstiMasterPort; + + if(!prt->portEnabled) + { + PTSM_to_TRANSMIT_INIT(prt, false); + return; + } + + 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: + PTSM_to_IDLE(prt); /* UnConditional Transition */ + return; + case PTSM_IDLE: + /* allTransmitReady = true; */ + ptp = GET_CIST_PTP_FROM_PORT(prt); + if(!ptp->selected || ptp->updtInfo) + { + /* allTransmitReady = false; */ + return; + } + assign(cistRole, ptp->role); + mstiMasterPort = false; + list_for_each_entry_continue(ptp, &prt->trees, port_list) + { + if(!ptp->selected || ptp->updtInfo) + { + /* allTransmitReady = false; */ + return; + } + if(roleMaster == ptp->role) + mstiMasterPort = true; + } + if(0 == prt->helloWhen) + { + PTSM_to_TRANSMIT_PERIODIC(prt); + return; + } + if(!(prt->txCount < prt->bridge->Transmit_Hold_Count)) + return; + if(prt->sendRSTP) + { /* implement MSTP */ + if(prt->newInfo || (prt->newInfoMsti && !mstiMasterPort)) + { + PTSM_to_TRANSMIT_RSTP(prt); + return; + } + } + else + { /* fallback to STP */ + if(prt->newInfo && (roleDesignated == cistRole)) + { + PTSM_to_TRANSMIT_CONFIG(prt); + return; + } + if(prt->newInfo && (roleRoot == cistRole)) + { + PTSM_to_TRANSMIT_TCN(prt); + return; + } + } + return; + } +} + +/* 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 void PISM_run(per_tree_port_t *ptp); +#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; + + assign(ptp->rcvdMsg, boolFalse); + assign(ptp->proposing, boolFalse); + assign(ptp->proposed, boolFalse); + assign(ptp->agree, boolFalse); + assign(ptp->agreed, boolFalse); + assign(ptp->rcvdInfoWhile, 0u); + assign(ptp->infoIs, ioDisabled); + assign(ptp->reselect, boolTrue); + assign(ptp->selected, boolFalse); + + if(!begin) + PISM_run(ptp); +} + +static void PISM_to_AGED(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_AGED; + + assign(ptp->infoIs, ioAged); + assign(ptp->reselect, boolTrue); + assign(ptp->selected, boolFalse); + + PISM_run(ptp); +} + +static void PISM_to_UPDATE(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_UPDATE; + + assign(ptp->proposing, boolFalse); + assign(ptp->proposed, boolFalse); + ptp->agreed = ptp->agreed && betterorsameInfo(ptp, ioMine); + ptp->synced = ptp->synced && ptp->agreed; + assign(ptp->portPriority, ptp->designatedPriority); + assign(ptp->portTimes, ptp->designatedTimes); + assign(ptp->updtInfo, boolFalse); + assign(ptp->infoIs, ioMine); + /* newInfoXst = TRUE; */ + port_t *prt = ptp->port; + if(0 == ptp->MSTID) + assign(prt->newInfo, boolTrue); + else + assign(prt->newInfoMsti, boolTrue); + + PISM_run(ptp); +} + +static void PISM_to_SUPERIOR_DESIGNATED(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_SUPERIOR_DESIGNATED; + + port_t *prt = ptp->port; + + assign(prt->infoInternal, prt->rcvdInternal); + assign(ptp->agreed, boolFalse); + assign(ptp->proposing, boolFalse); + 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); + assign(ptp->infoIs, ioReceived); + assign(ptp->reselect, boolTrue); + assign(ptp->selected, boolFalse); + assign(ptp->rcvdMsg, boolFalse); + + PISM_run(ptp); +} + +static void PISM_to_REPEATED_DESIGNATED(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_REPEATED_DESIGNATED; + + port_t *prt = ptp->port; + + assign(prt->infoInternal, prt->rcvdInternal); + recordProposal(ptp); + setTcFlags(ptp); + recordAgreement(ptp); + updtRcvdInfoWhile(ptp); + assign(ptp->rcvdMsg, boolFalse); + + PISM_run(ptp); +} + +static void PISM_to_INFERIOR_DESIGNATED(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_INFERIOR_DESIGNATED; + + recordDispute(ptp); + assign(ptp->rcvdMsg, boolFalse); + + PISM_run(ptp); +} + +static void PISM_to_NOT_DESIGNATED(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_NOT_DESIGNATED; + + recordAgreement(ptp); + setTcFlags(ptp); + assign(ptp->rcvdMsg, boolFalse); + + PISM_run(ptp); +} + +static void PISM_to_OTHER(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_OTHER; + + assign(ptp->rcvdMsg, boolFalse); + + PISM_run(ptp); +} + +static void PISM_to_CURRENT(per_tree_port_t *ptp) +{ + PISM_LOG(""); + ptp->PISM_state = PISM_CURRENT; + + PISM_run(ptp); +} + +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); +} + +static void PISM_run(per_tree_port_t *ptp) +{ + bool rcvdXstMsg, updtXstInfo; + port_t *prt = ptp->port; + + if((!prt->portEnabled) && (ioDisabled != ptp->infoIs)) + { + PISM_to_DISABLED(ptp, false); + return; + } + + switch(ptp->PISM_state) + { + case PISM_DISABLED: + if(prt->portEnabled) + { + PISM_to_AGED(ptp); + return; + } + if(ptp->rcvdMsg) + PISM_to_DISABLED(ptp, false); + return; + case PISM_AGED: + if(ptp->selected && ptp->updtInfo) + PISM_to_UPDATE(ptp); + return; + 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: + PISM_to_CURRENT(ptp); + return; + 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) + { + PISM_to_RECEIVE(ptp); + return; + } + if((ioReceived == ptp->infoIs) && (0 == ptp->rcvdInfoWhile) + && !ptp->updtInfo && !rcvdXstMsg) + { + PISM_to_AGED(ptp); + return; + } + if(ptp->selected && ptp->updtInfo) + PISM_to_UPDATE(ptp); + return; + case PISM_RECEIVE: + switch(ptp->rcvdInfo) + { + case SuperiorDesignatedInfo: + PISM_to_SUPERIOR_DESIGNATED(ptp); + return; + case RepeatedDesignatedInfo: + PISM_to_REPEATED_DESIGNATED(ptp); + return; + case InferiorDesignatedInfo: + PISM_to_INFERIOR_DESIGNATED(ptp); + return; + case InferiorRootAlternateInfo: + PISM_to_NOT_DESIGNATED(ptp); + return; + case OtherInfo: + PISM_to_OTHER(ptp); + return; + } + return; + } +} + +/* 13.33 Port Role Selection state machine */ + +static void PRSSM_run(tree_t *tree); +#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); */ +} + +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); */ +} + +static void PRSSM_run(tree_t *tree) +{ + per_tree_port_t *ptp; + + switch(tree->PRSSM_state) + { + case PRSSM_INIT_TREE: + PRSSM_to_ROLE_SELECTION(tree); + return; + case PRSSM_ROLE_SELECTION: + FOREACH_PTP_IN_TREE(ptp, tree) + if(ptp->reselect) + { + PRSSM_to_ROLE_SELECTION(tree); + return; + } + return; + } +} + +/* 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 void PRTSM_runr(per_tree_port_t *ptp, bool recursive_call); +#define PRTSM_run(ptp) PRTSM_runr((ptp), false) +#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); + + assign(ptp->role, roleDisabled); + assign(ptp->learn, boolFalse); + assign(ptp->forward, boolFalse); + assign(ptp->synced, boolFalse); + assign(ptp->sync, boolTrue); + assign(ptp->reRoot, boolTrue); + /* 13.25.6 */ + FwdDelay = __be16_to_cpu(cist->designatedTimes.Forward_Delay); + assign(ptp->rrWhile, FwdDelay); + /* 13.25.8 */ + MaxAge = __be16_to_cpu(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); */ +} + +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. + */ + assign(ptp->role, roleDisabled); + assign(ptp->learn, boolFalse); + assign(ptp->forward, boolFalse); + + PRTSM_runr(ptp, true); +} + +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); + assign(ptp->synced, boolTrue); + assign(ptp->rrWhile, 0u); + assign(ptp->sync, boolFalse); + assign(ptp->reRoot, boolFalse); + + PRTSM_runr(ptp, true); +} + + /* 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); + assign(ptp->proposed, boolFalse); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_MASTER_AGREED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_MASTER_AGREED; + + assign(ptp->proposed, boolFalse); + assign(ptp->sync, boolFalse); + assign(ptp->agree, boolTrue); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_MASTER_SYNCED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_MASTER_SYNCED; + + assign(ptp->rrWhile, 0u); + assign(ptp->synced, boolTrue); + assign(ptp->sync, boolFalse); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_MASTER_RETIRED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_MASTER_RETIRED; + + assign(ptp->reRoot, boolFalse); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_MASTER_FORWARD(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_MASTER_FORWARD; + + assign(ptp->forward, boolTrue); + assign(ptp->fdWhile, 0u); + assign(ptp->agreed, ptp->port->sendRSTP); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_MASTER_LEARN(per_tree_port_t *ptp, unsigned int forwardDelay) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_MASTER_LEARN; + + assign(ptp->learn, boolTrue); + assign(ptp->fdWhile, forwardDelay); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_MASTER_DISCARD(per_tree_port_t *ptp, unsigned int forwardDelay) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_MASTER_DISCARD; + + assign(ptp->learn, boolFalse); + assign(ptp->forward, boolFalse); + assign(ptp->disputed, boolFalse); + assign(ptp->fdWhile, forwardDelay); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_MASTER_PORT(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_MASTER_PORT; + + assign(ptp->role, roleMaster); + + PRTSM_runr(ptp, true); +} + + /* 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); + assign(ptp->proposed, boolFalse); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_ROOT_AGREED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_ROOT_AGREED; + + assign(ptp->proposed, boolFalse); + assign(ptp->sync, boolFalse); + assign(ptp->agree, boolTrue); + /* newInfoXst = TRUE; */ + port_t *prt = ptp->port; + if(0 == ptp->MSTID) + assign(prt->newInfo, boolTrue); + else + assign(prt->newInfoMsti, boolTrue); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_ROOT_SYNCED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_ROOT_SYNCED; + + assign(ptp->synced, boolTrue); + assign(ptp->sync, boolFalse); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_REROOT(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_REROOT; + + setReRootTree(ptp->tree); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_ROOT_FORWARD(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_ROOT_FORWARD; + + assign(ptp->fdWhile, 0u); + assign(ptp->forward, boolTrue); + + PRTSM_runr(ptp, true); +} + +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); + assign(ptp->learn, boolTrue); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_REROOTED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_REROOTED; + + assign(ptp->reRoot, boolFalse); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_ROOT_PORT(per_tree_port_t *ptp, unsigned int FwdDelay) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_ROOT_PORT; + + assign(ptp->role, roleRoot); + assign(ptp->rrWhile, FwdDelay); + + PRTSM_runr(ptp, true); +} + + /* 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; + + assign(ptp->proposing, boolTrue); + /* newInfoXst = TRUE; */ + if(0 == ptp->MSTID) + { /* CIST */ + /* 13.25.8. This tree is CIST. */ + unsigned int MaxAge = __be16_to_cpu(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); + assign(prt->newInfo, boolTrue); + } + else + assign(prt->newInfoMsti, boolTrue); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_DESIGNATED_AGREED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DESIGNATED_AGREED; + + assign(ptp->proposed, boolFalse); + assign(ptp->sync, boolFalse); + assign(ptp->agree, boolTrue); + /* newInfoXst = TRUE; */ + port_t *prt = ptp->port; + if(0 == ptp->MSTID) + assign(prt->newInfo, boolTrue); + else + assign(prt->newInfoMsti, boolTrue); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_DESIGNATED_SYNCED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DESIGNATED_SYNCED; + + assign(ptp->rrWhile, 0u); + assign(ptp->synced, boolTrue); + assign(ptp->sync, boolFalse); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_DESIGNATED_RETIRED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DESIGNATED_RETIRED; + + assign(ptp->reRoot, boolFalse); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_DESIGNATED_FORWARD(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DESIGNATED_FORWARD; + + assign(ptp->forward, boolTrue); + assign(ptp->fdWhile, 0u); + assign(ptp->agreed, ptp->port->sendRSTP); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_DESIGNATED_LEARN(per_tree_port_t *ptp, unsigned int forwardDelay) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DESIGNATED_LEARN; + + assign(ptp->learn, boolTrue); + assign(ptp->fdWhile, forwardDelay); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_DESIGNATED_DISCARD(per_tree_port_t *ptp, unsigned int forwardDelay) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DESIGNATED_DISCARD; + + assign(ptp->learn, boolFalse); + assign(ptp->forward, boolFalse); + assign(ptp->disputed, boolFalse); + assign(ptp->fdWhile, forwardDelay); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_DESIGNATED_PORT(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_DESIGNATED_PORT; + + assign(ptp->role, roleDesignated); + + PRTSM_runr(ptp, true); +} + + /* AlternatePort and BackupPort role transitions */ + +static void PRTSM_to_BLOCK_PORT(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_BLOCK_PORT; + + assign(ptp->role, ptp->selectedRole); + assign(ptp->learn, boolFalse); + assign(ptp->forward, boolFalse); + + PRTSM_runr(ptp, true); +} + +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); +} + +static void PRTSM_to_ALTERNATE_PROPOSED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_ALTERNATE_PROPOSED; + + setSyncTree(ptp->tree); + assign(ptp->proposed, boolFalse); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_to_ALTERNATE_AGREED(per_tree_port_t *ptp) +{ + PRTSM_LOG(""); + ptp->PRTSM_state = PRTSM_ALTERNATE_AGREED; + + assign(ptp->proposed, boolFalse); + assign(ptp->agree, boolTrue); + /* newInfoXst = TRUE; */ + port_t *prt = ptp->port; + if(0 == ptp->MSTID) + assign(prt->newInfo, boolTrue); + else + assign(prt->newInfoMsti, boolTrue); + + PRTSM_runr(ptp, true); +} + +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); + assign(ptp->synced, boolTrue); + assign(ptp->rrWhile, 0u); + assign(ptp->sync, boolFalse); + assign(ptp->reRoot, boolFalse); + + PRTSM_runr(ptp, true); +} + +static void PRTSM_runr(per_tree_port_t *ptp, bool recursive_call) +{ + /* 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 = __be16_to_cpu(cist->designatedTimes.Forward_Delay); + + /* 13.25.7 */ + HelloTime = __be16_to_cpu(cist->portTimes.Hello_Time); + + /* 13.25.d) -> 17.20.5 of 802.1D */ + forwardDelay = prt->sendRSTP ? HelloTime : FwdDelay; + + /* 13.25.8 */ + MaxAge = __be16_to_cpu(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: + PRTSM_to_DISABLE_PORT(ptp); + return; + case roleMaster: + PRTSM_to_MASTER_PORT(ptp); + return; + case roleRoot: + PRTSM_to_ROOT_PORT(ptp, FwdDelay); + return; + case roleDesignated: + PRTSM_to_DESIGNATED_PORT(ptp); + return; + case roleAlternate: + case roleBackup: + PRTSM_to_BLOCK_PORT(ptp); + return; + } + } + + /* 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: + PRTSM_to_DISABLE_PORT(ptp); + return; + case PRTSM_DISABLE_PORT: + if(ptp->selected && !ptp->updtInfo + && !ptp->learning && !ptp->forwarding + ) + PRTSM_to_DISABLED_PORT(ptp, MaxAge); + return; + case PRTSM_DISABLED_PORT: + if(ptp->selected && !ptp->updtInfo + && (ptp->sync || ptp->reRoot || !ptp->synced + || (ptp->fdWhile != MaxAge)) + ) + PRTSM_to_DISABLED_PORT(ptp, MaxAge); + return; + /* 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: + PRTSM_to_MASTER_PORT(ptp); + return; + case PRTSM_MASTER_PORT: + if(!(ptp->selected && !ptp->updtInfo)) + return; + if(ptp->reRoot && (0 == ptp->rrWhile)) + { + PRTSM_to_MASTER_RETIRED(ptp); + return; + } + if((!ptp->learning && !ptp->forwarding && !ptp->synced) + || (ptp->agreed && !ptp->synced) + || (prt->operEdge && !ptp->synced) + || (ptp->sync && ptp->synced) + ) + { + PRTSM_to_MASTER_SYNCED(ptp); + return; + } + if((allSynced && !ptp->agree) + || (ptp->proposed && ptp->agree) + ) + { + PRTSM_to_MASTER_AGREED(ptp); + return; + } + if(ptp->proposed && !ptp->agree) + { + PRTSM_to_MASTER_PROPOSED(ptp); + return; + } + if(((0 == ptp->fdWhile) || allSynced) + && ptp->learn && !ptp->forward + ) + { + PRTSM_to_MASTER_FORWARD(ptp); + return; + } + if(((0 == ptp->fdWhile) || allSynced) + && !ptp->learn + ) + { + PRTSM_to_MASTER_LEARN(ptp, forwardDelay); + return; + } + if(((ptp->sync && !ptp->synced) + || (ptp->reRoot && (0 != ptp->rrWhile)) + || ptp->disputed + ) + && !prt->operEdge && (ptp->learn || ptp->forward) + ) + { + PRTSM_to_MASTER_DISCARD(ptp, forwardDelay); + return; + } + return; + /* 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: + PRTSM_to_ROOT_PORT(ptp, FwdDelay); + return; + case PRTSM_ROOT_PORT: + if(!(ptp->selected && !ptp->updtInfo)) + return; + if(!ptp->forward && !ptp->reRoot) + { + PRTSM_to_REROOT(ptp); + return; + } + if((ptp->agreed && !ptp->synced) || (ptp->sync && ptp->synced)) + { + PRTSM_to_ROOT_SYNCED(ptp); + return; + } + if((allSynced && !ptp->agree) || (ptp->proposed && ptp->agree)) + { + PRTSM_to_ROOT_AGREED(ptp); + return; + } + if(ptp->proposed && !ptp->agree) + { + PRTSM_to_ROOT_PROPOSED(ptp); + return; + } + /* 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) + { + PRTSM_to_ROOT_LEARN(ptp, forwardDelay); + return; + } + else if(!ptp->forward) + { + PRTSM_to_ROOT_FORWARD(ptp); + return; + } + } + if(ptp->reRoot && ptp->forward) + { + PRTSM_to_REROOTED(ptp); + return; + } + if(ptp->rrWhile != FwdDelay) + { + PRTSM_to_ROOT_PORT(ptp, FwdDelay); + return; + } + return; + /* 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: + PRTSM_to_DESIGNATED_PORT(ptp); + return; + case PRTSM_DESIGNATED_PORT: + if(!(ptp->selected && !ptp->updtInfo)) + return; + if(ptp->reRoot && (0 == ptp->rrWhile)) + { + PRTSM_to_DESIGNATED_RETIRED(ptp); + return; + } + if((!ptp->learning && !ptp->forwarding && !ptp->synced) + || (ptp->agreed && !ptp->synced) + || (prt->operEdge && !ptp->synced) + || (ptp->sync && ptp->synced) + ) + { + PRTSM_to_DESIGNATED_SYNCED(ptp); + return; + } + if(allSynced && (ptp->proposed || !ptp->agree)) + { + PRTSM_to_DESIGNATED_AGREED(ptp); + return; + } + if(!ptp->forward && !ptp->agreed && !ptp->proposing + && !prt->operEdge) + { + PRTSM_to_DESIGNATED_PROPOSE(ptp); + return; + } + if(((0 == ptp->fdWhile) || ptp->agreed || prt->operEdge) + && ((0 == ptp->rrWhile) || !ptp->reRoot) && !ptp->sync + ) + { + if(!ptp->learn) + { + PRTSM_to_DESIGNATED_LEARN(ptp, forwardDelay); + return; + } + else if(!ptp->forward) + { + PRTSM_to_DESIGNATED_FORWARD(ptp); + return; + } + } + if(((ptp->sync && !ptp->synced) + || (ptp->reRoot && (0 != ptp->rrWhile)) + || ptp->disputed + ) + && !prt->operEdge && (ptp->learn || ptp->forward) + ) + { + PRTSM_to_DESIGNATED_DISCARD(ptp, forwardDelay); + return; + } + return; + /* AlternatePort and BackupPort role transitions */ + case PRTSM_BLOCK_PORT: + if(ptp->selected && !ptp->updtInfo + && !ptp->learning && !ptp->forwarding + ) + PRTSM_to_ALTERNATE_PORT(ptp, forwardDelay); + return; + case PRTSM_BACKUP_PORT: + /* return; */ + case PRTSM_ALTERNATE_PROPOSED: + /* return; */ + case PRTSM_ALTERNATE_AGREED: + PRTSM_to_ALTERNATE_PORT(ptp, forwardDelay); + return; + case PRTSM_ALTERNATE_PORT: + if(!(ptp->selected && !ptp->updtInfo)) + return; + if((allSynced && !ptp->agree) || (ptp->proposed && ptp->agree)) + { + PRTSM_to_ALTERNATE_AGREED(ptp); + return; + } + if(ptp->proposed && !ptp->agree) + { + PRTSM_to_ALTERNATE_PROPOSED(ptp); + return; + } + if((ptp->rbWhile != 2 * HelloTime) && (roleBackup == ptp->role)) + { + PRTSM_to_BACKUP_PORT(ptp, HelloTime); + return; + } + if((ptp->fdWhile != forwardDelay) || ptp->sync || ptp->reRoot + || !ptp->synced) + { + PRTSM_to_ALTERNATE_PORT(ptp, forwardDelay); + return; + } + return; + } +} + +/* 13.35 Port State Transition state machine */ + +static void PSTSM_run(per_tree_port_t *ptp); +#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) + MSTP_OUT_set_state(ptp, BR_STATE_BLOCKING); + assign(ptp->learning, boolFalse); + assign(ptp->forwarding, boolFalse); + + if(!begin) + PSTSM_run(ptp); +} + +static void PSTSM_to_LEARNING(per_tree_port_t *ptp) +{ + ptp->PSTSM_state = PSTSM_LEARNING; + + /* enableLearning(); */ + if(BR_STATE_LEARNING != ptp->state) + MSTP_OUT_set_state(ptp, BR_STATE_LEARNING); + assign(ptp->learning, boolTrue); + + PSTSM_run(ptp); +} + +static void PSTSM_to_FORWARDING(per_tree_port_t *ptp) +{ + ptp->PSTSM_state = PSTSM_FORWARDING; + + /* enableForwarding(); */ + if(BR_STATE_FORWARDING != ptp->state) + MSTP_OUT_set_state(ptp, BR_STATE_FORWARDING); + assign(ptp->forwarding, boolTrue); + + /* No need to run, no one condition will be met + PSTSM_run(ptp); */ +} + +static void PSTSM_run(per_tree_port_t *ptp) +{ + switch(ptp->PSTSM_state) + { + case PSTSM_DISCARDING: + if(ptp->learn) + PSTSM_to_LEARNING(ptp); + return; + case PSTSM_LEARNING: + if(!ptp->learn) + PSTSM_to_DISCARDING(ptp, false); + else if(ptp->forward) + PSTSM_to_FORWARDING(ptp); + return; + case PSTSM_FORWARDING: + if(!ptp->forward) + PSTSM_to_DISCARDING(ptp, false); + return; + } +} + +/* 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); + if(0 == ptp->MSTID) /* CIST */ + assign(ptp->port->tcAck, boolFalse); + + if(!begin) + TCSM_run(ptp); +} + +static void TCSM_to_LEARNING(per_tree_port_t *ptp) +{ + ptp->TCSM_state = TCSM_LEARNING; + + if(0 == ptp->MSTID) /* CIST */ + { + port_t *prt = ptp->port; + assign(prt->rcvdTcn, boolFalse); + assign(prt->rcvdTcAck, boolFalse); + } + assign(ptp->rcvdTc, boolFalse); + assign(ptp->tcProp, boolFalse); + + TCSM_run(ptp); +} + +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) + assign(prt->newInfo, boolTrue); + else + assign(prt->newInfoMsti, boolTrue); + + TCSM_run(ptp); +} + +static void TCSM_to_NOTIFIED_TCN(per_tree_port_t *ptp) +{ + ptp->TCSM_state = TCSM_NOTIFIED_TCN; + + newTcWhile(ptp); + + TCSM_run(ptp); +} + +static void TCSM_to_NOTIFIED_TC(per_tree_port_t *ptp) +{ + ptp->TCSM_state = TCSM_NOTIFIED_TC; + + assign(ptp->rcvdTc, boolFalse); + if(0 == ptp->MSTID) /* CIST */ + { + port_t *prt = ptp->port; + assign(prt->rcvdTcn, boolFalse); + if(roleDesignated == ptp->role) + assign(prt->tcAck, boolTrue); + } + setTcPropTree(ptp); + + TCSM_run(ptp); +} + +static void TCSM_to_PROPAGATING(per_tree_port_t *ptp) +{ + ptp->TCSM_state = TCSM_PROPAGATING; + + newTcWhile(ptp); + set_fdbFlush(ptp); + assign(ptp->tcProp, boolFalse); + + TCSM_run(ptp); +} + +static void TCSM_to_ACKNOWLEDGED(per_tree_port_t *ptp) +{ + ptp->TCSM_state = TCSM_ACKNOWLEDGED; + + assign(ptp->tcWhile, 0u); + set_TopologyChange(ptp->tree, false); + assign(ptp->port->rcvdTcAck, boolFalse); + + TCSM_run(ptp); +} + +static void TCSM_to_ACTIVE(per_tree_port_t *ptp) +{ + ptp->TCSM_state = TCSM_ACTIVE; + + TCSM_run(ptp); +} + +static void TCSM_run(per_tree_port_t *ptp) +{ + bool active_port; + port_t *prt = ptp->port; + + switch(ptp->TCSM_state) + { + case TCSM_INACTIVE: + if(ptp->learn && !ptp->fdbFlush) + TCSM_to_LEARNING(ptp); + return; + case TCSM_LEARNING: + active_port = (roleRoot == ptp->role) + || (roleDesignated == ptp->role) + || (roleMaster == ptp->role); + if(active_port && ptp->forward && !prt->operEdge) + { + TCSM_to_DETECTED(ptp); + return; + } + if(ptp->rcvdTc || prt->rcvdTcn || prt->rcvdTcAck || ptp->tcProp) + { + TCSM_to_LEARNING(ptp); + return; + } + else if(!active_port && !(ptp->learn || ptp->learning)) + TCSM_to_INACTIVE(ptp, false); + return; + case TCSM_NOTIFIED_TCN: + TCSM_to_NOTIFIED_TC(ptp); + return; + case TCSM_DETECTED: + /* return; */ + case TCSM_NOTIFIED_TC: + /* return; */ + case TCSM_PROPAGATING: + /* return; */ + case TCSM_ACKNOWLEDGED: + TCSM_to_ACTIVE(ptp); + return; + case TCSM_ACTIVE: + active_port = (roleRoot == ptp->role) + || (roleDesignated == ptp->role) + || (roleMaster == ptp->role); + if(!active_port || prt->operEdge) + { + TCSM_to_LEARNING(ptp); + return; + } + if(prt->rcvdTcn) + { + TCSM_to_NOTIFIED_TCN(ptp); + return; + } + if(ptp->rcvdTc) + { + TCSM_to_NOTIFIED_TC(ptp); + return; + } + if(ptp->tcProp/* && !prt->operEdge */) + { + TCSM_to_PROPAGATING(ptp); + return; + } + if(prt->rcvdTcAck) + { + TCSM_to_ACKNOWLEDGED(ptp); + return; + } + return; + } +} + +/* 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); + + /* 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 */ +static void br_state_machines_run(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_run(prt); + /* 13.29 Port Protocol Migration state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + PPMSM_run(prt); + /* 13.30 Bridge Detection state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + BDSM_run(prt); + /* 13.31 Port Transmit state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + PTSM_run(prt); + + /* 13.32 Port Information state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + FOREACH_PTP_IN_PORT(ptp, prt) + PISM_run(ptp); + } + + /* 13.33 Port Role Selection state machine */ + FOREACH_TREE_IN_BRIDGE(tree, br) + PRSSM_run(tree); + + /* 13.34 Port Role Transitions state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + FOREACH_PTP_IN_PORT(ptp, prt) + PRTSM_run(ptp); + } + /* 13.35 Port State Transition state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + FOREACH_PTP_IN_PORT(ptp, prt) + PSTSM_run(ptp); + } + /* 13.36 Topology Change state machine */ + FOREACH_PORT_IN_BRIDGE(prt, br) + { + FOREACH_PTP_IN_PORT(ptp, prt) + TCSM_run(ptp); + } +} diff --git a/mstp.h b/mstp.h new file mode 100644 index 0000000..65fff8c --- /dev/null +++ b/mstp.h @@ -0,0 +1,756 @@ +/* + * 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 "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) + +/* Stricter version of bool constants, for the assign macro */ +#define boolFalse ((bool)false) +#define boolTrue ((bool)true) + +/* 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(unsigned char * text, int text_len, unsigned char * key, + int key_len, caddr_t 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; + +/* 17.14 of 802.1D, Table 17-1 */ +#define MIN_COMPAT_HELLO_TIME 1 + +typedef struct +{ + __u8 remainingHops; + /* not used for MSTIs, only for CIST */ + __be16 Forward_Delay; + __be16 Max_Age; + __be16 Message_Age; + __be16 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; + __be16 MessageAge; + __be16 MaxAge; + __be16 HelloTime; + __be16 ForwardDelay; + /* 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 +{ + _io_Disabled, + _io_Mine, + _io_Aged, + _io_Received +} port_info_origin_t; + +typedef enum +{ + _pr_Disabled, + _pr_Root, + _pr_Designated, + _pr_Alternate, + _pr_Backup, + _pr_Master +} port_role_t; + +typedef enum +{ + encodedRoleMaster = 0, + encodedRoleAlternateBackup = 1, + encodedRoleRoot = 2, + encodedRoleDesignated = 3 +} port_encoded_role_t; + +typedef enum +{ + _pv_STP = 0, + _pv_RSTP = 2, + _pv_MSTP = 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; + +/* Stricter version of port_info_origin & port_role & protocol_version + * constants, for the assign macro */ +#define ioDisabled ((port_info_origin_t)_io_Disabled) +#define ioMine ((port_info_origin_t)_io_Mine) +#define ioAged ((port_info_origin_t)_io_Aged) +#define ioReceived ((port_info_origin_t)_io_Received) + +#define roleDisabled ((port_role_t)_pr_Disabled) +#define roleRoot ((port_role_t)_pr_Root) +#define roleDesignated ((port_role_t)_pr_Designated) +#define roleAlternate ((port_role_t)_pr_Alternate) +#define roleBackup ((port_role_t)_pr_Backup) +#define roleMaster ((port_role_t)_pr_Master) + +#define protoSTP ((protocol_version_t)_pv_STP) +#define protoRSTP ((protocol_version_t)_pv_RSTP) +#define protoMSTP ((protocol_version_t)_pv_MSTP) + +/* 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 */ + __u8 MaxHops; /* 13.22.o */ + protocol_version_t ForceProtocolVersion; /* 13.22.e */ + __be16 Forward_Delay; /* 13.22.f */ + __be16 Max_Age; /* 13.22.i */ + unsigned int Transmit_Hold_Count; /* 13.22.g */ + unsigned int Migrate_Time; /* 13.22.h */ + unsigned int rapidAgeingWhile; + + __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; + + /* 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 */ + + /* 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; + + sysdep_if_data_t sysdeps; +} 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; + + /* 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(bridge_t *br, int ageingTime); +void MSTP_OUT_tx_bpdu(port_t *prt, bpdu_t *bpdu, int size); + +/* 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; + bridge_identifier_t designated_root; + unsigned int root_path_cost; + port_identifier_t root_port_id; + unsigned int root_max_age; + unsigned int root_forward_delay; + unsigned int bridge_max_age; + unsigned int 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 */ + __u8 max_hops; +} 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; + 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 +{ + unsigned int bridge_max_age; + bool set_bridge_max_age; + + unsigned int 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; +} 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 */ + unsigned int 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 */ +} 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; +} 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 */ diff --git a/netif_utils.c b/netif_utils.c new file mode 100644 index 0000000..da26a6f --- /dev/null +++ b/netif_utils.c @@ -0,0 +1,150 @@ +/***************************************************************************** + 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 "log.h" + +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); + 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 ethtool_get_link(char *ifname) +{ + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + struct ethtool_value eval; + + eval.cmd = ETHTOOL_GLINK; + ifr.ifr_data = (caddr_t)&eval; + if(0 > ioctl(netsock, SIOCETHTOOL, &ifr)) + { + ERROR("Cannot get link status for %s: %m\n", ifname); + return -1; + } + if(eval.data) + 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); + 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 = ecmd.speed; /* Ethtool speed is in Mbps */ + *duplex = ecmd.duplex; /* We have same convention as ethtool. + 0 = half, 1 = full */ + return 0; +} + +/********* Sysfs based utility functions *************/ + +/* This sysfs stuff might break with interface renames */ +bool is_bridge(char *if_name) +{ + char path[32 + IFNAMSIZ]; + sprintf(path, "/sys/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, "/sys/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; +} diff --git a/netif_utils.h b/netif_utils.h new file mode 100644 index 0000000..b2919c8 --- /dev/null +++ b/netif_utils.h @@ -0,0 +1,42 @@ +/***************************************************************************** + 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 ethtool_get_speed_duplex(char *ifname, int *speed, int *duplex); +int ethtool_get_link(char *ifname); + +bool is_bridge(char *if_name); + +int get_bridge_portno(char *if_name); + +#endif /* NETIF_UTILS_H */ diff --git a/packet.c b/packet.c new file mode 100644 index 0000000..0c08ae7 --- /dev/null +++ b/packet.c @@ -0,0 +1,187 @@ +/***************************************************************************** + 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 "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 = htons(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 + { + packet_event.fd = s; + packet_event.handler = packet_rcv; + + if(0 == add_epoll(&packet_event)) + return 0; + } + + close(s); + return -1; +} diff --git a/packet.h b/packet.h new file mode 100644 index 0000000..8d2e0ff --- /dev/null +++ b/packet.h @@ -0,0 +1,33 @@ +/***************************************************************************** + 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 */